From 1f4f10c72321e4c7f5023edeb3ac630f976d1fe0 Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Thu, 9 Jul 2020 14:56:13 +0200 Subject: [PATCH 01/28] Add a tab level in advanced settings --- apps/launcher/advancedpage.cpp | 337 ++++-- .../reference/modding/settings/camera.rst | 2 +- .../reference/modding/settings/game.rst | 2 +- files/ui/advancedpage.ui | 1073 +++++++++-------- 4 files changed, 791 insertions(+), 623 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 00d0df048..07fe8ddd6 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -4,9 +4,43 @@ #include #include #include +#include #include #include +#include + + +class HorizontalTextWestTabStyle : public QProxyStyle +{ +public: + QSize sizeFromContents(ContentsType type, const QStyleOption* option, const QSize& size, const QWidget* widget) const + { + QSize s = QProxyStyle::sizeFromContents(type, option, size, widget); + if (type == QStyle::CT_TabBarTab) + { + s.transpose(); + s.setHeight(s.height() + 20); + } + return s; + } + + void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const + { + if (element == CE_TabBarTabLabel) + { + if (const QStyleOptionTab* tab = qstyleoption_cast(option)) + { + QStyleOptionTab opt(*tab); + opt.shape = QTabBar::RoundedNorth; + QProxyStyle::drawControl(element, &opt, painter, widget); + return; + } + } + QProxyStyle::drawControl(element, option, painter, widget); + } +}; + Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Settings::Manager &engineSettings, QWidget *parent) @@ -19,6 +53,7 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, setupUi(this); loadSettings(); + AdvancedTabWidget->tabBar()->setStyle(new HorizontalTextWestTabStyle); mCellNameCompleter.setModel(&mCellNameCompleterModel); startDefaultCharacterAtField->setCompleter(&mCellNameCompleter); } @@ -55,142 +90,220 @@ void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked() runScriptAfterStartupField->setText(path); } +namespace +{ + constexpr double CellSizeInUnits = 8192; + + double convertToCells(double unitRadius) + { + return std::round((unitRadius / 0.93 + 1024) / CellSizeInUnits); + } + + double convertToUnits(double CellGridRadius) + { + return (CellSizeInUnits * CellGridRadius - 1024) * 0.93; + } +} + bool Launcher::AdvancedPage::loadSettings() { - // Testing - bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1; - if (skipMenu) { - skipMenuCheckBox->setCheckState(Qt::Checked); - } - startDefaultCharacterAtLabel->setEnabled(skipMenu); - startDefaultCharacterAtField->setEnabled(skipMenu); - - startDefaultCharacterAtField->setText(mGameSettings.value("start")); - runScriptAfterStartupField->setText(mGameSettings.value("script-run")); - - // Game Settings - loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); - loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); - loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); - loadSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game"); - loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); - loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); - loadSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game"); - int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game"); - if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2) - unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex); - loadSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game"); - loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); - loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game"); - connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool))); - loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); - if (animSourcesCheckBox->checkState()) + // Game mechanics { - loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); - loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); + loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); + loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); + loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); + loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); + loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); + loadSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game"); + loadSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game"); + loadSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game"); + loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game"); + loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game"); + int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game"); + if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2) + unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex); } - loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game"); - loadSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game"); - // Input Settings - loadSettingBool(grabCursorCheckBox, "grab cursor", "Input"); - loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); + // Visuals + { + loadSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); + loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); + connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool))); + loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); + if (animSourcesCheckBox->checkState()) + { + loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); + loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); + } + loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera"); + loadSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game"); - // Saves Settings - loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); - maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves")); + const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain"); + const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain"); + if (distantTerrain && objectPaging) { + distantLandCheckBox->setCheckState(Qt::Checked); + } - // User Interface Settings - loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); - loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); - loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); - loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); - loadSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); - int showOwnedIndex = mEngineSettings.getInt("show owned", "Game"); - // Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid. - if (showOwnedIndex >= 0 && showOwnedIndex <= 3) - showOwnedComboBox->setCurrentIndex(showOwnedIndex); + loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain"); + viewingDistanceComboBox->setValue(convertToCells(mEngineSettings.getInt("viewing distance", "Camera"))); + } - // Other Settings - QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper(); - if (screenshotFormatComboBox->findText(screenshotFormatString) == -1) - screenshotFormatComboBox->addItem(screenshotFormatString); - screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString)); + // Interface Changes + { + loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); + loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); + loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); + loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); + loadSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); + int showOwnedIndex = mEngineSettings.getInt("show owned", "Game"); + // Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid. + if (showOwnedIndex >= 0 && showOwnedIndex <= 3) + showOwnedComboBox->setCurrentIndex(showOwnedIndex); + } + // Bug fixes + { + loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); + loadSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game"); + } + + // Miscellaneous + { + // Saves + loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); + maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves")); + + // Other Settings + QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper(); + if (screenshotFormatComboBox->findText(screenshotFormatString) == -1) + screenshotFormatComboBox->addItem(screenshotFormatString); + screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString)); + } + + // Testing + { + loadSettingBool(grabCursorCheckBox, "grab cursor", "Input"); + + bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1; + if (skipMenu) + { + skipMenuCheckBox->setCheckState(Qt::Checked); + } + startDefaultCharacterAtLabel->setEnabled(skipMenu); + startDefaultCharacterAtField->setEnabled(skipMenu); + + startDefaultCharacterAtField->setText(mGameSettings.value("start")); + runScriptAfterStartupField->setText(mGameSettings.value("script-run")); + } return true; } void Launcher::AdvancedPage::saveSettings() { - // Ensure we only set the new settings if they changed. This is to avoid cluttering the - // user settings file (which by definition should only contain settings the user has touched) + // Game mechanics + { + saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); + saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); + saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); + saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); + saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); + saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game"); + saveSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game"); + saveSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game"); + saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game"); + saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game"); + int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex(); + if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game")) + mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex); + } + + // Visuals + { + saveSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); + saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); + saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); + saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); + saveSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); + saveSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera"); + saveSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game"); + + const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain"); + const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain"); + const bool wantDistantLand = distantLandCheckBox->checkState(); + if (wantDistantLand != (distantTerrain && objectPaging)) { + mEngineSettings.setBool("distant terrain", "Terrain", wantDistantLand); + mEngineSettings.setBool("object paging", "Terrain", wantDistantLand); + } + + saveSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain"); + double viewingDistance = viewingDistanceComboBox->value(); + if (viewingDistance != convertToCells(mEngineSettings.getInt("viewing distance", "Camera"))) + { + mEngineSettings.setInt("viewing distance", "Camera", convertToUnits(viewingDistance)); + } + } + + // Interface Changes + { + saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); + saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); + saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); + saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); + saveSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); + int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); + if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) + mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); + } + + // Bug fixes + { + saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); + saveSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game"); + } + + // Miscellaneous + { + // Saves Settings + saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); + int maximumQuicksaves = maximumQuicksavesComboBox->value(); + if (maximumQuicksaves != mEngineSettings.getInt("max quicksaves", "Saves")) + { + mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves); + } + + // Other Settings + std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); + if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General")) + mEngineSettings.setString("screenshot format", "General", screenshotFormatString); + } // Testing - int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked; - if (skipMenu != mGameSettings.value("skip-menu").toInt()) - mGameSettings.setValue("skip-menu", QString::number(skipMenu)); + { + saveSettingBool(grabCursorCheckBox, "grab cursor", "Input"); - QString startCell = startDefaultCharacterAtField->text(); - if (startCell != mGameSettings.value("start")) { - mGameSettings.setValue("start", startCell); + int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked; + if (skipMenu != mGameSettings.value("skip-menu").toInt()) + mGameSettings.setValue("skip-menu", QString::number(skipMenu)); + + QString startCell = startDefaultCharacterAtField->text(); + if (startCell != mGameSettings.value("start")) + { + mGameSettings.setValue("start", startCell); + } + QString scriptRun = runScriptAfterStartupField->text(); + if (scriptRun != mGameSettings.value("script-run")) + mGameSettings.setValue("script-run", scriptRun); } - QString scriptRun = runScriptAfterStartupField->text(); - if (scriptRun != mGameSettings.value("script-run")) - mGameSettings.setValue("script-run", scriptRun); - - // Game Settings - saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); - saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); - saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); - saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); - saveSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game"); - saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); - saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game"); - int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex(); - if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game")) - mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex); - saveSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game"); - saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); - saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game"); - saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); - saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); - saveSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); - saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game"); - saveSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game"); - - // Input Settings - saveSettingBool(grabCursorCheckBox, "grab cursor", "Input"); - saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); - - // Saves Settings - saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); - int maximumQuicksaves = maximumQuicksavesComboBox->value(); - if (maximumQuicksaves != mEngineSettings.getInt("max quicksaves", "Saves")) { - mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves); - } - - // User Interface Settings - saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); - saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); - saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); - saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); - saveSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); - int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); - if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) - mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); - - // Other Settings - std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); - if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General")) - mEngineSettings.setString("screenshot format", "General", screenshotFormatString); } -void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) { +void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) +{ if (mEngineSettings.getBool(setting, group)) checkbox->setCheckState(Qt::Checked); } -void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) { +void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) +{ bool cValue = checkbox->checkState(); if (cValue != mEngineSettings.getBool(setting, group)) mEngineSettings.setBool(setting, group, cValue); diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index 18b6754a7..8d7078905 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -136,7 +136,7 @@ This setting controls third person view mode. False: View is centered on the character's head. Crosshair is hidden. True: In non-combat mode camera is positioned behind the character's shoulder. Crosshair is visible in third person mode as well. -This setting can only be configured by editing the settings configuration file. +This setting can be controlled in Advanced tab of the launcher. view over shoulder offset ------------------------- diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 8d0b0dfc1..072c614d4 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -329,7 +329,7 @@ If disabled then the whole character's body is pointed to the direction of view. If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it also changes straight right and straight left movement. -This setting can only be configured by editing the settings configuration file. +This setting can be controlled in Advanced tab of the launcher. swim upward coef ---------------- diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 142cd7259..de8e3dfc0 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -4,280 +4,466 @@ - - - true + + + QTabWidget::West - + + 0 + + + + Game mechanics + + + + + + <html><head/><body><p>This setting causes the behavior of the sneak key (bound to Ctrl by default) to toggle sneaking on and off rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled. </p></body></html> + + + Toggle sneak + + + + + + + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> + + + Can loot during death animation + + + + + + + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> + + + Followers defend immediately + + + + + + + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> + + + Soulgem values rebalance + + + + + + + <html><head/><body><p>Make enchanted weaponry without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + + + Enchanted weapons are magical + + + + + + + <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> + + + Permanent barter disposition changes + + + + + + + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored -- like in Morrowind.</p></body></html> + + + Classic reflected Absorb spells behavior + + + + + + + <html><head/><body><p>Allow non-standard ammunition solely to bypass normal weapon resistance or weakness.</p></body></html> + + + Only appropriate ammunition bypasses normal weapon resistance + + + + + + + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> + + + Uncapped Damage Fatigue + + + + + + + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + + + Racial variation in speed fix + + + + + + + <html><head/><body><p>Factor strength into hand-to-hand damage calculations, as the MCP formula: damage * (strength / 40).</p><p>The default value is Off.</p></body></html> + + + + + + Factor strength into hand-to-hand combat: + + + + + + + 0 + + + + Off + + + + + Affect werewolves + + + + + Do not affect werewolves + + + + + + + + + + + Qt::Vertical + + + + + + + + Visuals + + + + + + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. +Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. +Affected objects will use shaders. +</p></body></html> + + + Bump/reflect map local lighting + + + + + + + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + + + Use magic item animation + + + + + + + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + + + Use additional animation sources + + + + + + + + 20 + + + + + false + + + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + + + Weapon sheathing + + + + + + + false + + + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + + + Shield sheathing + + + + + + + + + + <html><head/><body><p>This setting controls third person view mode.</p><p>False: View is centered on the character's head. Crosshair is hidden. +True: In non-combat mode camera is positioned behind the character's shoulder. Crosshair is visible in third person mode as well. +</p></body></html> + + + View over the shoulder + + + + + + + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it also changes straight right and straight left movement.</p></body></html> + + + Turn to movement direction + + + + + + + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + + + Distant land + + + + + + + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + + + Active grid object paging + + + + + + + <html><head/><body><p>This value controls the maximum visible distance (in cell units). +Larger values significantly improve rendering in exterior spaces, +but also increase the amount of rendered geometry and significantly reduce the frame rate.</p></body></html> + + + + + + Viewing distance + + + + + + + 0.0 + + + 0.5 + + + Cells + + + + + + + + + + Qt::Vertical + + + + + + + + Interface changes + + + + + + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + + + Show effect duration + + + + + + + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + + + Show enchant chance + + + + + + + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + Show melee info + + + + + + + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + Show projectile damage + + + + + + + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + + + Change dialogue topic color + + + + + + + <html><head/><body><p>Enable visual clues for items owned by NPCs when the crosshair is on the object.</p><p>The default value is Off.</p></body></html> + + + + + + Show owned: + + + + + + + 1 + + + + Off + + + + + Tool Tip Only + + + + + Crosshair Only + + + + + Tool Tip and Crosshair + + + + + + + + + + + Qt::Vertical + + + + + + + + Bug fixes + + + + + + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + + + Merchant equipping fix + + + + + + + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + + + Trainers choose their training skills based on their base skill points + + + + + + + Qt::Vertical + + + + + + + + Miscellaneous + - - - - Game Mechanics - - - - - - <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> - - - Can loot during death animation - - - - - - - <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - - - Followers attack on sight - - - - - - - <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - - - Prevent merchant equipping - - - - - - - <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> - - - Rebalance soul gem values - - - - - - - <html><head/><body><p>Make enchanted weaponry without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - - - Enchanted weapons are magical - - - - - - - <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> - - - Barter disposition change is permanent - - - - - - - <html><head/><body><p>Effects of reflected Absorb spells are not mirrored -- like in Morrowind.</p></body></html> - - - Classic reflected Absorb spells behavior - - - - - - - <html><head/><body><p>Allow non-standard ammunition solely to bypass normal weapon resistance or weakness.</p></body></html> - - - Only appropriate ammunition bypasses normal weapon resistance - - - - - - - <html><head/><body><p>Factor strength into hand-to-hand damage calculations, as the MCP formula: damage * (strength / 40).</p><p>The default value is Off.</p></body></html> - - - - -1 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Factor strength into hand-to-hand combat: - - - - - - - 0 - - - - Off - - - - - Affect werewolves - - - - - Do not affect werewolves - - - - - - - - - - - <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - - - Use magic item animation - - - - - - - <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> - - - Normalise race speed - - - - - - - <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - - - Use additional animation sources - - - - - - - - 6 - - - 20 - - - 0 - - - 0 - - - 0 - - - - - false - - - <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - - - Weapon sheathing - - - - - - - false - - - <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - - - Shield sheathing - - - - - - - - - - <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> - - - Uncapped Damage Fatigue - - - - - - - <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - - - Trainers choose their training skills based on their base skill points - - - - - - - - - - Input - - - - - - <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - - - Grab cursor - - - - - - - <html><head/><body><p>This setting causes the behavior of the sneak key (bound to Ctrl by default) to toggle sneaking on and off rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled. </p></body></html> - - - Toggle sneak - - - - - - @@ -295,26 +481,11 @@ - + <html><head/><body><p>This setting determines how many quicksave and autosave slots you can have at a time. If greater than 1, quicksaves will be sequentially created each time you quicksave. Once the maximum number of quicksaves has been reached, the oldest quicksave will be recycled the next time you perform a quicksave.</p></body></html> - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - @@ -335,211 +506,6 @@ - - - - Testing - - - - - - These settings are intended for testing mods and will cause issues if used for normal gameplay. - - - true - - - - - - - Qt::Horizontal - - - - - - - Skip menu and generate default character - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 20 - 20 - - - - - - - - Start default character at - - - - - - - default cell - - - - - - - - - Run script after startup: - - - - - - - - - - - - Browse… - - - - - - - - - - - - User Interface - - - - - - <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - - - Show effect duration - - - - - - - <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - - - Show enchant chance - - - - - - - <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - - - Show melee info - - - - - - - <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - - - Show projectile damage - - - - - - - <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - - - Change dialogue topic color - - - - - - - <html><head/><body><p>Enable visual clues for items owned by NPCs when the crosshair is on the object.</p><p>The default value is Off.</p></body></html> - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Show owned: - - - - - - - 1 - - - - Off - - - - - Tool Tip Only - - - - - Crosshair Only - - - - - Tool Tip and Crosshair - - - - - - - - - - @@ -547,26 +513,11 @@ - + <html><head/><body><p>Specify the format for screen shots taken by pressing the screen shot key (bound to F12 by default). This setting should be the file extension commonly associated with the desired format. The formats supported will be determined at compilation, but “jpg”, “png”, and “tga” should be allowed.</p></body></html> - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - @@ -599,6 +550,110 @@ + + + + Qt::Vertical + + + + + + + + Testing + + + + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + + + true + + + + + + + Qt::Horizontal + + + + + + + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + + + Grab cursor + + + + + + + Skip menu and generate default character + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + + + + Start default character at + + + + + + + default cell + + + + + + + + + Run script after startup: + + + + + + + + + + + + Browse… + + + + + + + + + Qt::Vertical + + + From 1ff2256bc1899cfdd9345027568c5aff8d573fa9 Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Thu, 16 Jul 2020 16:17:25 +0200 Subject: [PATCH 02/28] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e75b20dad..6fe149c7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Feature #5445: Handle NiLines Feature #5457: Realistic diagonal movement Feature #5486: Fixes trainers to choose their training skills based on their base skill points + Feature #5519: Code Patch tab in launcher Feature #5524: Resume failed script execution after reload Feature #5525: Search fields tweaks (utf-8) Task #5480: Drop Qt4 support From 71b00da289c2f3ae44ef10786dcc313815b860e5 Mon Sep 17 00:00:00 2001 From: David Cernat Date: Sat, 25 Jul 2020 21:33:52 +0300 Subject: [PATCH 03/28] Update AUTHORS with project leader history & special mention for scrawl --- AUTHORS.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 0f0522c44..b662f0abf 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -10,7 +10,10 @@ If you feel your name is missing from this list, please notify a developer. Programmers ----------- - Marc Zinnschlag (Zini) - Lead Programmer/Project Manager + Bret Curtis (psi29a) - Project leader 2019-present + Marc Zinnschlag (Zini) - Project leader 2010-2018 + Nicolay Korslund - Project leader 2008-2010 + scrawl - Top contributor Adam Hogan (aurix) Aesylwinn @@ -39,7 +42,6 @@ Programmers Austin Salgat (Salgat) Ben Shealy (bentsherman) Berulacks - Bret Curtis (psi29a) Britt Mathis (galdor557) Capostrophic Carl Maxwell @@ -146,7 +148,6 @@ Programmers Nathan Jeffords (blunted2night) NeveHanter Nialsy - Nicolay Korslund Nikolay Kasyanov (corristo) nobrakal Nolan Poe (nopoe) @@ -175,7 +176,6 @@ Programmers Roman Siromakha (elsid) Sandy Carter (bwrsandman) Scott Howard (maqifrnswa) - scrawl Sebastian Wick (swick) Sergey Fukanchik Sergey Shambir (sergey-shambir) From 224923d6c8d34f0fa1639f97c52dc1399cc36dca Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sun, 26 Jul 2020 01:56:55 +0200 Subject: [PATCH 04/28] update appdata license and dejavufont license file --- .../mygui/{DejaVu Font License.txt => DejaVuFontLicense.txt} | 0 files/openmw.appdata.xml | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename files/mygui/{DejaVu Font License.txt => DejaVuFontLicense.txt} (100%) diff --git a/files/mygui/DejaVu Font License.txt b/files/mygui/DejaVuFontLicense.txt similarity index 100% rename from files/mygui/DejaVu Font License.txt rename to files/mygui/DejaVuFontLicense.txt diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml index edbeb1a27..f4bdb5c91 100644 --- a/files/openmw.appdata.xml +++ b/files/openmw.appdata.xml @@ -5,8 +5,8 @@ Copyright 2020 Bret Curtis --> org.openmw.launcher.desktop - CC0-1.0 - GPL-3.0 and MIT + GPL-3+ + GPL-3+ OpenMW Unofficial open source engine re-implementation of the game Morrowind From 4c9cefefdd707bb2c0c41998dbf69a9dfb3d69a0 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Tue, 30 Jun 2020 21:53:59 +0300 Subject: [PATCH 05/28] Get rid of NifOsg::CollisionSwitch --- components/nifosg/nifloader.cpp | 40 ++++++------------------------ components/sceneutil/serialize.cpp | 1 - 2 files changed, 7 insertions(+), 34 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8266b5e01..04aa91a1f 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -170,31 +170,6 @@ namespace namespace NifOsg { - class CollisionSwitch : public osg::MatrixTransform - { - public: - CollisionSwitch() : osg::MatrixTransform() - { - } - - CollisionSwitch(const CollisionSwitch& copy, const osg::CopyOp& copyop) - : osg::MatrixTransform(copy, copyop) - { - } - - META_Node(NifOsg, CollisionSwitch) - - CollisionSwitch(const osg::Matrixf& transformations, bool enabled) : osg::MatrixTransform(transformations) - { - setEnabled(enabled); - } - - void setEnabled(bool enabled) - { - setNodeMask(enabled ? ~0 : Loader::getIntersectionDisabledNodeMask()); - } - }; - bool Loader::sShowMarkers = false; void Loader::setShowMarkers(bool show) @@ -501,14 +476,6 @@ namespace NifOsg case Nif::RC_NiBillboardNode: dataVariance = osg::Object::DYNAMIC; break; - case Nif::RC_NiCollisionSwitch: - { - bool enabled = nifNode->flags & Nif::NiNode::Flag_ActiveCollision; - node = new CollisionSwitch(nifNode->trafo.toMatrix(), enabled); - // This matrix transform must not be combined with another matrix transform. - dataVariance = osg::Object::DYNAMIC; - break; - } default: // The Root node can be created as a Group if no transformation is required. // This takes advantage of the fact root nodes can't have additional controllers @@ -523,6 +490,13 @@ namespace NifOsg if (!node) node = new osg::MatrixTransform(nifNode->trafo.toMatrix()); + if (nifNode->recType == Nif::RC_NiCollisionSwitch && !(nifNode->flags & Nif::NiNode::Flag_ActiveCollision)) + { + node->setNodeMask(Loader::getIntersectionDisabledNodeMask()); + // This node must not be combined with another node. + dataVariance = osg::Object::DYNAMIC; + } + node->setDataVariance(dataVariance); return node; diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 60f096a72..f84a19876 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -131,7 +131,6 @@ void registerSerializers() "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", - "NifOsg::CollisionSwitch", "osgMyGUI::Drawable", "osg::DrawCallback", "osgOQ::ClearQueriesCallback", From a61267f57d8c2a4ca7341f127d6f0017a0692455 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Tue, 30 Jun 2020 23:27:46 +0300 Subject: [PATCH 06/28] Replace NodeUserData with a custom transform node --- components/CMakeLists.txt | 2 +- components/nifosg/controller.cpp | 14 ++++---- components/nifosg/controller.hpp | 6 +--- components/nifosg/matrixtransform.cpp | 20 +++++++++++ components/nifosg/matrixtransform.hpp | 34 +++++++++++++++++++ components/nifosg/nifloader.cpp | 14 ++------ components/nifosg/particle.cpp | 13 +++----- components/nifosg/userdata.hpp | 48 --------------------------- components/sceneutil/clone.cpp | 11 ------ components/sceneutil/clone.hpp | 2 -- components/sceneutil/serialize.cpp | 12 ++++++- 11 files changed, 79 insertions(+), 97 deletions(-) create mode 100644 components/nifosg/matrixtransform.cpp create mode 100644 components/nifosg/matrixtransform.hpp delete mode 100644 components/nifosg/userdata.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 86d657792..4627ea2f0 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -59,7 +59,7 @@ add_component_dir (nif ) add_component_dir (nifosg - nifloader controller particle userdata + nifloader controller particle matrixtransform ) add_component_dir (nifbullet diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 3c95394a6..a4db2cba3 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -4,14 +4,13 @@ #include #include #include -#include #include #include #include -#include "userdata.hpp" +#include "matrixtransform.hpp" namespace NifOsg { @@ -119,13 +118,12 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) { if (hasInput()) { - osg::MatrixTransform* trans = static_cast(node); + NifOsg::MatrixTransform* trans = static_cast(node); osg::Matrix mat = trans->getMatrix(); float time = getInputValue(nv); - NodeUserData* userdata = static_cast(trans->getUserDataContainer()->getUserObject(0)); - Nif::Matrix3& rot = userdata->mRotationScale; + Nif::Matrix3& rot = trans->mRotationScale; bool setRot = false; if(!mRotations.empty()) @@ -140,18 +138,18 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) } else { - // no rotation specified, use the previous value from the UserData + // no rotation specified, use the previous value for (int i=0;i<3;++i) for (int j=0;j<3;++j) mat(j,i) = rot.mValues[i][j]; // NB column/row major difference } - if (setRot) // copy the new values back to the UserData + if (setRot) // copy the new values back for (int i=0;i<3;++i) for (int j=0;j<3;++j) rot.mValues[i][j] = mat(j,i); // NB column/row major difference - float& scale = userdata->mScale; + float& scale = trans->mScale; if(!mScales.empty()) scale = mScales.interpKey(time); diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index c81f97a71..df1086f56 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -9,11 +9,9 @@ #include #include -#include //UVController +#include -// FlipController #include -#include #include #include @@ -22,8 +20,6 @@ namespace osg { - class Node; - class StateSet; class Material; } diff --git a/components/nifosg/matrixtransform.cpp b/components/nifosg/matrixtransform.cpp new file mode 100644 index 000000000..96450313d --- /dev/null +++ b/components/nifosg/matrixtransform.cpp @@ -0,0 +1,20 @@ +#include "matrixtransform.hpp" + +namespace NifOsg +{ + MatrixTransform::MatrixTransform(int recordIndex, const Nif::Transformation &trafo) + : osg::MatrixTransform(trafo.toMatrix()) + , mIndex(recordIndex) + , mScale(trafo.scale) + , mRotationScale(trafo.rotation) + { + } + + MatrixTransform::MatrixTransform(const MatrixTransform ©, const osg::CopyOp ©op) + : osg::MatrixTransform(copy, copyop) + , mIndex(copy.mIndex) + , mScale(copy.mScale) + , mRotationScale(copy.mRotationScale) + { + } +} diff --git a/components/nifosg/matrixtransform.hpp b/components/nifosg/matrixtransform.hpp new file mode 100644 index 000000000..61d2438de --- /dev/null +++ b/components/nifosg/matrixtransform.hpp @@ -0,0 +1,34 @@ +#ifndef OPENMW_COMPONENTS_NIFOSG_MATRIXTRANSFORM_H +#define OPENMW_COMPONENTS_NIFOSG_MATRIXTRANSFORM_H + +#include + +#include + +namespace NifOsg +{ + + class MatrixTransform : public osg::MatrixTransform + { + public: + using osg::MatrixTransform::MatrixTransform; + MatrixTransform(int recordIndex, const Nif::Transformation &trafo); + MatrixTransform(const MatrixTransform ©, const osg::CopyOp ©op); + + META_Node(NifOsg, MatrixTransform) + + // NIF record index + int mIndex{0}; + + // Hack: account for Transform differences between OSG and NIFs. + // OSG uses a 4x4 matrix, NIF's use a 3x3 rotationScale, float scale, and vec3 position. + // Decomposing the original components from the 4x4 matrix isn't possible, which causes + // problems when a KeyframeController wants to change only one of these components. So + // we store the scale and rotation components separately here. + float mScale{0.f}; + Nif::Matrix3 mRotationScale; + }; + +} + +#endif diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 04aa91a1f..68e3f9a9c 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -43,8 +42,8 @@ #include #include +#include "matrixtransform.hpp" #include "particle.hpp" -#include "userdata.hpp" namespace { @@ -488,7 +487,7 @@ namespace NifOsg break; } if (!node) - node = new osg::MatrixTransform(nifNode->trafo.toMatrix()); + node = new NifOsg::MatrixTransform(nifNode->recIndex, nifNode->trafo); if (nifNode->recType == Nif::RC_NiCollisionSwitch && !(nifNode->flags & Nif::NiNode::Flag_ActiveCollision)) { @@ -523,15 +522,6 @@ namespace NifOsg if (!rootNode) rootNode = node; - // UserData used for a variety of features: - // - finding the correct emitter node for a particle system - // - establishing connections to the animated collision shapes, which are handled in a separate loader - // - finding a random child NiNode in NiBspArrayController - // - storing the previous 3x3 rotation and scale values for when a KeyframeController wants to - // change only certain elements of the 4x4 transform - node->getOrCreateUserDataContainer()->addUserObject( - new NodeUserData(nifNode->recIndex, nifNode->trafo.scale, nifNode->trafo.rotation)); - for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next) { if(e->recType == Nif::RC_NiTextKeyExtraData && textKeys) diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index f71dcdd96..7c7277118 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -11,7 +11,7 @@ #include #include -#include "userdata.hpp" +#include "matrixtransform.hpp" namespace NifOsg { @@ -381,16 +381,11 @@ void FindGroupByRecIndex::apply(osg::Geometry &node) void FindGroupByRecIndex::applyNode(osg::Node &searchNode) { - if (searchNode.getUserDataContainer() && searchNode.getUserDataContainer()->getNumUserObjects()) + if (NifOsg::MatrixTransform* trans = dynamic_cast(&searchNode)) { - NodeUserData* holder = dynamic_cast(searchNode.getUserDataContainer()->getUserObject(0)); - if (holder && holder->mIndex == mRecIndex) + if (trans->mIndex == mRecIndex) { - osg::Group* group = searchNode.asGroup(); - if (!group) - group = searchNode.getParent(0); - - mFound = group; + mFound = trans; mFoundPath = getNodePath(); return; } diff --git a/components/nifosg/userdata.hpp b/components/nifosg/userdata.hpp deleted file mode 100644 index 42fcaff47..000000000 --- a/components/nifosg/userdata.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef OPENMW_COMPONENTS_NIFOSG_USERDATA_H -#define OPENMW_COMPONENTS_NIFOSG_USERDATA_H - -#include - -#include - -namespace NifOsg -{ - - // Note if you are copying a scene graph with this user data you should use the DEEP_COPY_USERDATA copyop. - class NodeUserData : public osg::Object - { - public: - NodeUserData(int index, float scale, const Nif::Matrix3& rotationScale) - : mIndex(index), mScale(scale), mRotationScale(rotationScale) - { - } - NodeUserData() - : mIndex(0), mScale(0) - { - } - NodeUserData(const NodeUserData& copy, const osg::CopyOp& copyop) - : Object(copy, copyop) - , mIndex(copy.mIndex) - , mScale(copy.mScale) - , mRotationScale(copy.mRotationScale) - { - } - - META_Object(NifOsg, NodeUserData) - - // NIF record index - int mIndex; - - // Hack: account for Transform differences between OSG and NIFs. - // OSG uses a 4x4 matrix, NIF's use a 3x3 rotationScale, float scale, and vec3 position. - // Decomposing the original components from the 4x4 matrix isn't possible, which causes - // problems when a KeyframeController wants to change only one of these components. So - // we store the scale and rotation components separately here. - // Note for a cleaner solution it would be possible to write a custom Transform node - float mScale; - Nif::Matrix3 mRotationScale; - }; - -} - -#endif diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index c3261515d..1de7bfd91 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -6,8 +6,6 @@ #include #include -#include - #include #include @@ -22,15 +20,6 @@ namespace SceneUtil | osg::CopyOp::DEEP_COPY_USERDATA); } - osg::Object* CopyOp::operator ()(const osg::Object* node) const - { - // We should copy node transformations when we copy node - if (dynamic_cast(node)) - return static_cast(node->clone(*this)); - - return osg::CopyOp::operator()(node); - } - osg::Node* CopyOp::operator ()(const osg::Node* node) const { if (const osgParticle::ParticleProcessor* processor = dynamic_cast(node)) diff --git a/components/sceneutil/clone.hpp b/components/sceneutil/clone.hpp index cf6d79e68..8c5fbd351 100644 --- a/components/sceneutil/clone.hpp +++ b/components/sceneutil/clone.hpp @@ -30,8 +30,6 @@ namespace SceneUtil virtual osg::Node* operator() (const osg::Node* node) const; virtual osg::Drawable* operator() (const osg::Drawable* drawable) const; - virtual osg::Object* operator ()(const osg::Object* node) const; - private: // maps new pointers to their old pointers // a little messy, but I think this should be the most efficient way diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index f84a19876..398d9cc0c 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -1,5 +1,6 @@ #include "serialize.hpp" +#include #include #include @@ -74,6 +75,15 @@ public: } }; +class MatrixTransformSerializer : public osgDB::ObjectWrapper +{ +public: + MatrixTransformSerializer() + : osgDB::ObjectWrapper(createInstanceFunc, "NifOsg::MatrixTransform", "osg::Object osg::Node osg::Transform osg::MatrixTransform NifOsg::MatrixTransform") + { + } +}; + osgDB::ObjectWrapper* makeDummySerializer(const std::string& classname) { return new osgDB::ObjectWrapper(createInstanceFunc, classname, "osg::Object"); @@ -100,6 +110,7 @@ void registerSerializers() mgr->addWrapper(new MorphGeometrySerializer); mgr->addWrapper(new LightManagerSerializer); mgr->addWrapper(new CameraRelativeTransformSerializer); + mgr->addWrapper(new MatrixTransformSerializer); // Don't serialize Geometry data as we are more interested in the overall structure rather than tons of vertex data that would make the file large and hard to read. mgr->removeWrapper(mgr->findWrapper("osg::Geometry")); @@ -118,7 +129,6 @@ void registerSerializers() "SceneUtil::StateSetUpdater", "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", - "NifOsg::NodeUserData", "NifOsg::FlipController", "NifOsg::KeyframeController", "NifOsg::TextKeyMapHolder", From 3b55d657e56780160a64f6d08b5446a30cbbe157 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Tue, 30 Jun 2020 23:30:22 +0300 Subject: [PATCH 07/28] CopyRigVisitor: Log the number of parents in multiple parents error --- components/sceneutil/attach.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index c438e705d..2a6ec84e5 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -64,7 +64,7 @@ namespace SceneUtil for (const osg::ref_ptr& node : mToCopy) { if (node->getNumParents() > 1) - Log(Debug::Error) << "Error CopyRigVisitor: node has multiple parents"; + Log(Debug::Error) << "Error CopyRigVisitor: node has " << node->getNumParents() << " parents"; while (node->getNumParents()) node->getParent(0)->removeChild(node); From cc791af0f5102445bb9a5547a03c8dc4edfe25c0 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 11 Jul 2020 17:55:15 +0300 Subject: [PATCH 08/28] Serialization fixes Make sure NifOsg::MatrixTransform serialization behaves as intended Add a dummy serializer for NifOsg::UVController --- components/sceneutil/serialize.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 398d9cc0c..1577f080f 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -1,9 +1,10 @@ #include "serialize.hpp" -#include #include #include +#include + #include #include #include @@ -79,7 +80,7 @@ class MatrixTransformSerializer : public osgDB::ObjectWrapper { public: MatrixTransformSerializer() - : osgDB::ObjectWrapper(createInstanceFunc, "NifOsg::MatrixTransform", "osg::Object osg::Node osg::Transform osg::MatrixTransform NifOsg::MatrixTransform") + : osgDB::ObjectWrapper(createInstanceFunc, "NifOsg::MatrixTransform", "osg::Object osg::Node osg::Transform osg::MatrixTransform NifOsg::MatrixTransform") { } }; @@ -141,6 +142,7 @@ void registerSerializers() "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", + "NifOsg::UVController", "osgMyGUI::Drawable", "osg::DrawCallback", "osgOQ::ClearQueriesCallback", From ad87289d59fc84306d67fb7d106e21f672dec905 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 11 Jul 2020 20:15:13 +0300 Subject: [PATCH 09/28] Fix NifOsg::MatrixTransform constructor inheritance --- components/nifosg/matrixtransform.cpp | 5 +++++ components/nifosg/matrixtransform.hpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/components/nifosg/matrixtransform.cpp b/components/nifosg/matrixtransform.cpp index 96450313d..b61df834b 100644 --- a/components/nifosg/matrixtransform.cpp +++ b/components/nifosg/matrixtransform.cpp @@ -2,6 +2,11 @@ namespace NifOsg { + MatrixTransform::MatrixTransform() + : osg::MatrixTransform() + { + } + MatrixTransform::MatrixTransform(int recordIndex, const Nif::Transformation &trafo) : osg::MatrixTransform(trafo.toMatrix()) , mIndex(recordIndex) diff --git a/components/nifosg/matrixtransform.hpp b/components/nifosg/matrixtransform.hpp index 61d2438de..86c903491 100644 --- a/components/nifosg/matrixtransform.hpp +++ b/components/nifosg/matrixtransform.hpp @@ -11,7 +11,7 @@ namespace NifOsg class MatrixTransform : public osg::MatrixTransform { public: - using osg::MatrixTransform::MatrixTransform; + MatrixTransform(); MatrixTransform(int recordIndex, const Nif::Transformation &trafo); MatrixTransform(const MatrixTransform ©, const osg::CopyOp ©op); From f93655e803d1b611f53477872f41a37eac5a8679 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 11 Jul 2020 20:59:21 +0300 Subject: [PATCH 10/28] Encapsulate NIF transform changes in NifOsg::MatrixTransform --- components/nifosg/controller.cpp | 47 +++++++-------------------- components/nifosg/matrixtransform.cpp | 33 +++++++++++++++++++ components/nifosg/matrixtransform.hpp | 13 ++++++++ 3 files changed, 57 insertions(+), 36 deletions(-) diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index a4db2cba3..203951edd 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -119,48 +119,23 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) if (hasInput()) { NifOsg::MatrixTransform* trans = static_cast(node); - osg::Matrix mat = trans->getMatrix(); float time = getInputValue(nv); - Nif::Matrix3& rot = trans->mRotationScale; - - bool setRot = false; - if(!mRotations.empty()) - { - mat.setRotate(mRotations.interpKey(time)); - setRot = true; - } + if (!mRotations.empty()) + trans->updateRotation(mRotations.interpKey(time)); else if (!mXRotations.empty() || !mYRotations.empty() || !mZRotations.empty()) - { - mat.setRotate(getXYZRotation(time)); - setRot = true; - } - else - { - // no rotation specified, use the previous value - for (int i=0;i<3;++i) - for (int j=0;j<3;++j) - mat(j,i) = rot.mValues[i][j]; // NB column/row major difference - } + trans->updateRotation(getXYZRotation(time)); + else // no rotation specified, use the previous value + trans->applyCurrentRotation(); - if (setRot) // copy the new values back - for (int i=0;i<3;++i) - for (int j=0;j<3;++j) - rot.mValues[i][j] = mat(j,i); // NB column/row major difference + if (!mScales.empty()) + trans->updateScale(mScales.interpKey(time)); + else // no scale specified, use the previous value + trans->applyCurrentScale(); - float& scale = trans->mScale; - if(!mScales.empty()) - scale = mScales.interpKey(time); - - for (int i=0;i<3;++i) - for (int j=0;j<3;++j) - mat(i,j) *= scale; - - if(!mTranslations.empty()) - mat.setTrans(mTranslations.interpKey(time)); - - trans->setMatrix(mat); + if (!mTranslations.empty()) + trans->setTranslation(mTranslations.interpKey(time)); } traverse(node, nv); diff --git a/components/nifosg/matrixtransform.cpp b/components/nifosg/matrixtransform.cpp index b61df834b..12144b62a 100644 --- a/components/nifosg/matrixtransform.cpp +++ b/components/nifosg/matrixtransform.cpp @@ -22,4 +22,37 @@ namespace NifOsg , mRotationScale(copy.mRotationScale) { } + + void MatrixTransform::applyCurrentRotation() + { + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + _matrix(j,i) = mRotationScale.mValues[i][j]; // NB column/row major difference + } + + void MatrixTransform::applyCurrentScale() + { + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + _matrix(i,j) *= mScale; + } + + void MatrixTransform::updateRotation(const osg::Quat& rotation) + { + _matrix.setRotate(rotation); + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + mRotationScale.mValues[i][j] = _matrix(j,i); // NB column/row major difference + } + + void MatrixTransform::updateScale(const float scale) + { + mScale = scale; + applyCurrentScale(); + } + + void MatrixTransform::setTranslation(const osg::Vec3f& translation) + { + _matrix.setTrans(translation); + } } diff --git a/components/nifosg/matrixtransform.hpp b/components/nifosg/matrixtransform.hpp index 86c903491..b633efaad 100644 --- a/components/nifosg/matrixtransform.hpp +++ b/components/nifosg/matrixtransform.hpp @@ -17,9 +17,22 @@ namespace NifOsg META_Node(NifOsg, MatrixTransform) + // Apply the current NIF rotation or scale to OSG matrix. + void applyCurrentRotation(); + void applyCurrentScale(); + + // Apply the given rotation to OSG matrix directly and update NIF rotation matrix. + void updateRotation(const osg::Quat& rotation); + // Update current NIF scale and apply it to OSG matrix. + void updateScale(const float scale); + + // Apply the given translation to OSG matrix. + void setTranslation(const osg::Vec3f& translation); + // NIF record index int mIndex{0}; + private: // Hack: account for Transform differences between OSG and NIFs. // OSG uses a 4x4 matrix, NIF's use a 3x3 rotationScale, float scale, and vec3 position. // Decomposing the original components from the 4x4 matrix isn't possible, which causes From 46825e8a4d4c09d293505954ed534f2f02955e69 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 12 Jul 2020 15:34:22 +0300 Subject: [PATCH 11/28] Move NIF record index back to a separate user object This makes sure it's never erroneously optimized out. NodeIndexHolders don't need to be cloned as their record index is never supposed to be changed. --- components/nifosg/matrixtransform.cpp | 4 +-- components/nifosg/matrixtransform.hpp | 5 +--- components/nifosg/nifloader.cpp | 9 ++++++- components/nifosg/nodeindexholder.hpp | 35 +++++++++++++++++++++++++++ components/nifosg/particle.cpp | 13 +++++++--- components/sceneutil/serialize.cpp | 1 + 6 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 components/nifosg/nodeindexholder.hpp diff --git a/components/nifosg/matrixtransform.cpp b/components/nifosg/matrixtransform.cpp index 12144b62a..72e12ecf8 100644 --- a/components/nifosg/matrixtransform.cpp +++ b/components/nifosg/matrixtransform.cpp @@ -7,9 +7,8 @@ namespace NifOsg { } - MatrixTransform::MatrixTransform(int recordIndex, const Nif::Transformation &trafo) + MatrixTransform::MatrixTransform(const Nif::Transformation &trafo) : osg::MatrixTransform(trafo.toMatrix()) - , mIndex(recordIndex) , mScale(trafo.scale) , mRotationScale(trafo.rotation) { @@ -17,7 +16,6 @@ namespace NifOsg MatrixTransform::MatrixTransform(const MatrixTransform ©, const osg::CopyOp ©op) : osg::MatrixTransform(copy, copyop) - , mIndex(copy.mIndex) , mScale(copy.mScale) , mRotationScale(copy.mRotationScale) { diff --git a/components/nifosg/matrixtransform.hpp b/components/nifosg/matrixtransform.hpp index b633efaad..ac2fbb57a 100644 --- a/components/nifosg/matrixtransform.hpp +++ b/components/nifosg/matrixtransform.hpp @@ -12,7 +12,7 @@ namespace NifOsg { public: MatrixTransform(); - MatrixTransform(int recordIndex, const Nif::Transformation &trafo); + MatrixTransform(const Nif::Transformation &trafo); MatrixTransform(const MatrixTransform ©, const osg::CopyOp ©op); META_Node(NifOsg, MatrixTransform) @@ -29,9 +29,6 @@ namespace NifOsg // Apply the given translation to OSG matrix. void setTranslation(const osg::Vec3f& translation); - // NIF record index - int mIndex{0}; - private: // Hack: account for Transform differences between OSG and NIFs. // OSG uses a 4x4 matrix, NIF's use a 3x3 rotationScale, float scale, and vec3 position. diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 68e3f9a9c..f88800e36 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -43,6 +43,7 @@ #include #include "matrixtransform.hpp" +#include "nodeindexholder.hpp" #include "particle.hpp" namespace @@ -487,7 +488,7 @@ namespace NifOsg break; } if (!node) - node = new NifOsg::MatrixTransform(nifNode->recIndex, nifNode->trafo); + node = new NifOsg::MatrixTransform(nifNode->trafo); if (nifNode->recType == Nif::RC_NiCollisionSwitch && !(nifNode->flags & Nif::NiNode::Flag_ActiveCollision)) { @@ -522,6 +523,12 @@ namespace NifOsg if (!rootNode) rootNode = node; + // The original NIF record index is used for a variety of features: + // - finding the correct emitter node for a particle system + // - establishing connections to the animated collision shapes, which are handled in a separate loader + // - finding a random child NiNode in NiBspArrayController + node->getOrCreateUserDataContainer()->addUserObject(new NodeIndexHolder(nifNode->recIndex)); + for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next) { if(e->recType == Nif::RC_NiTextKeyExtraData && textKeys) diff --git a/components/nifosg/nodeindexholder.hpp b/components/nifosg/nodeindexholder.hpp new file mode 100644 index 000000000..e7d4f0db3 --- /dev/null +++ b/components/nifosg/nodeindexholder.hpp @@ -0,0 +1,35 @@ +#ifndef OPENMW_COMPONENTS_NIFOSG_NODEINDEXHOLDER_H +#define OPENMW_COMPONENTS_NIFOSG_NODEINDEXHOLDER_H + +#include + +namespace NifOsg +{ + + class NodeIndexHolder : public osg::Object + { + public: + NodeIndexHolder() = default; + NodeIndexHolder(int index) + : mIndex(index) + { + } + NodeIndexHolder(const NodeIndexHolder& copy, const osg::CopyOp& copyop) + : Object(copy, copyop) + , mIndex(copy.mIndex) + { + } + + META_Object(NifOsg, NodeIndexHolder) + + int getIndex() const { return mIndex; } + + private: + + // NIF record index + int mIndex{0}; + }; + +} + +#endif diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 7c7277118..0cbc3f22b 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -11,7 +11,7 @@ #include #include -#include "matrixtransform.hpp" +#include "nodeindexholder.hpp" namespace NifOsg { @@ -381,11 +381,16 @@ void FindGroupByRecIndex::apply(osg::Geometry &node) void FindGroupByRecIndex::applyNode(osg::Node &searchNode) { - if (NifOsg::MatrixTransform* trans = dynamic_cast(&searchNode)) + if (searchNode.getUserDataContainer() && searchNode.getUserDataContainer()->getNumUserObjects()) { - if (trans->mIndex == mRecIndex) + NodeIndexHolder* holder = dynamic_cast(searchNode.getUserDataContainer()->getUserObject(0)); + if (holder && holder->getIndex() == mRecIndex) { - mFound = trans; + osg::Group* group = searchNode.asGroup(); + if (!group) + group = searchNode.getParent(0); + + mFound = group; mFoundPath = getNodePath(); return; } diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 1577f080f..62325186c 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -143,6 +143,7 @@ void registerSerializers() "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", "NifOsg::UVController", + "NifOsg::NodeIndexHolder", "osgMyGUI::Drawable", "osg::DrawCallback", "osgOQ::ClearQueriesCallback", From c4eb50d17fe3425dbd7e381603ef7e1b2a9d103f Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 26 Jul 2020 11:33:34 +0300 Subject: [PATCH 12/28] Update documentation Document distant fog Make colored topic functionality documentation clearer Update the status of screenshot format tweakability --- .../source/reference/modding/settings/GUI.rst | 11 +- .../source/reference/modding/settings/fog.rst | 115 ++++++++++++++++++ .../reference/modding/settings/general.rst | 4 +- .../reference/modding/settings/index.rst | 1 + 4 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 docs/source/reference/modding/settings/fog.rst diff --git a/docs/source/reference/modding/settings/GUI.rst b/docs/source/reference/modding/settings/GUI.rst index bbe6b8336..349a98697 100644 --- a/docs/source/reference/modding/settings/GUI.rst +++ b/docs/source/reference/modding/settings/GUI.rst @@ -148,7 +148,8 @@ color topic enable :Range: True/False :Default: False -Control wether additionnal formatting will be applied to dialogs topic. See 'color topic specific' and 'color topic exhausted' for details. +This setting controls whether the topics available in the dialogue topic list are coloured according to their state. +See 'color topic specific' and 'color topic exhausted' for details. color topic specific -------------------- @@ -157,11 +158,11 @@ color topic specific :Range: 0.0 to 1.0 :Default: empty -This setting overrides the color of keywords in the dialogue topic window. +This setting overrides the colour of dialogue topics that have a response unique to the actors speaking. The value is composed of four floating point values representing the red, green, blue and alpha channels. The alpha value is currently ignored. -The color is overriden if the actor is about to give an answer that is unique to him (that is, dialogue with their object ID in the Actor field) that wasn't seen yet. +A topic response is considered unique if its Actor filter field contains the speaking actor's object ID and hasn't yet been read. color topic exhausted --------------------- @@ -170,8 +171,8 @@ color topic exhausted :Range: 0.0 to 1.0 :Default: empty -This setting overrides the color of keywords in the dialogue topic window. +This setting overrides the colour of dialogue topics which have been "exhausted" by the player. The value is composed of four floating point values representing the red, green, blue and alpha channels. The alpha value is currently ignored. -The color is overridden if the next actor responses to the topic keyword has already been seen by the player. +A topic is considered "exhausted" if the response the player is about to see has already been seen. diff --git a/docs/source/reference/modding/settings/fog.rst b/docs/source/reference/modding/settings/fog.rst new file mode 100644 index 000000000..10ab0bbe3 --- /dev/null +++ b/docs/source/reference/modding/settings/fog.rst @@ -0,0 +1,115 @@ +Fog Settings +############ + +use distant fog +--------------- + +:Type: boolean +:Range: True/False +:Default: False + +This setting overhauls the behavior of fog calculations. + +Normally the fog start and end distance are proportional to the viewing distance +and use the fog depth set in the fallback settings. + +Enabling this setting separates the fog distance from the viewing distance and fallback settings and makes fog distance +and apparent density dependent on the weather and the current location according to the settings below. + +Unfortunately specific weather-dependent fog factor and offset parameters are currently hard-coded. +They are based off the default settings of MGE XE. + ++--------------+------------+--------+ +| Weather Type | Fog Factor | Offset | ++==============+============+========+ +| Clear | 1.0 | 0.0 | ++--------------+------------+--------+ +| Cloudy | 0.9 | 0.0 | ++--------------+------------+--------+ +| Foggy | 0.2 | 0.3 | ++--------------+------------+--------+ +| Overcast | 0.7 | 0.0 | ++--------------+------------+--------+ +| Rain | 0.5 | 0.1 | ++--------------+------------+--------+ +| Thunderstorm | 0.5 | 0.2 | ++--------------+------------+--------+ +| Ashstorm | 0.2 | 0.5 | ++--------------+------------+--------+ +| Blight | 0.2 | 0.6 | ++--------------+------------+--------+ +| Snow | 0.5 | 0.4 | ++--------------+------------+--------+ +| Blizzard | 0.16 | 0.7 | ++--------------+------------+--------+ + +Non-underwater fog start and end distance are calculated like this according to these parameters:: + + fog start distance = fog factor * (base fog start distance - fog offset * base fog end distance) + fog end distance = fog factor * (1.0 - fog offset) * base fog end distance + +Underwater fog distance is used as-is. + +A negative fog start distance means that the fog starts behind the camera +so the entirety of the scene will be at least partially fogged. + +A negative fog end distance means that the fog ends behind the camera +so the entirety of the scene will be completely submerged in the fog. + +Fog end distance should be larger than the fog start distance. + +This setting and all further settings can only be configured by editing the settings configuration file. + +distant land fog start +---------------------- + +:Type: floating point +:Range: The whole range of 32-bit floating point +:Default: 16384 (2 cells) + +This is the base fog start distance used for distant fog calculations in exterior locations. + +distant land fog end +-------------------- + +:Type: floating point +:Range: The whole range of 32-bit floating point +:Default: 40960 (5 cells) + +This is the base fog end distance used for distant fog calculations in exterior locations. + +distant underwater fog start +---------------------------- + +:Type: floating point +:Range: The whole range of 32-bit floating point +:Default: -4096 + +This is the base fog start distance used for distant fog calculations in underwater locations. + +distant underwater fog end +-------------------------- + +:Type: floating point +:Range: The whole range of 32-bit floating point +:Default: 2457.6 + +This is the base fog end distance used for distant fog calculations in underwater locations. + +distant interior fog start +-------------------------- + +:Type: floating point +:Range: The whole range of 32-bit floating point +:Default: 0 + +This is the base fog start distance used for distant fog calculations in interior locations. + +distant interior fog end +------------------------ + +:Type: floating point +:Range: The whole range of 32-bit floating point +:Default: 16384 (2 cells) + +This is the base fog end distance used for distant fog calculations in interior locations. diff --git a/docs/source/reference/modding/settings/general.rst b/docs/source/reference/modding/settings/general.rst index be253d613..c885f77aa 100644 --- a/docs/source/reference/modding/settings/general.rst +++ b/docs/source/reference/modding/settings/general.rst @@ -29,7 +29,7 @@ Specify the format for screen shots taken by pressing the screen shot key (bound This setting should be the file extension commonly associated with the desired format. The formats supported will be determined at compilation, but "jpg", "png", and "tga" should be allowed. -This setting can only be configured by editing the settings configuration file. +This setting can be configured in Advanced tab of the launcher. texture mag filter ------------------ @@ -58,4 +58,4 @@ texture mipmap Set the texture mipmap type to control the method mipmaps are created. Mipmapping is a way of reducing the processing power needed during minification -by pregenerating a series of smaller textures. \ No newline at end of file +by pregenerating a series of smaller textures. diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index 53b097a54..a31ce0d53 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -41,6 +41,7 @@ The ranges included with each setting are the physically possible ranges, not re camera cells + fog map GUI HUD From 040a92c373536403ef0177e2bb9b7d46962fcf79 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 26 Jul 2020 11:07:18 +0200 Subject: [PATCH 13/28] implement additem/removeitem for non-unique actors --- apps/openmw/mwclass/creature.cpp | 5 ++ apps/openmw/mwclass/creature.hpp | 2 + apps/openmw/mwclass/npc.cpp | 5 ++ apps/openmw/mwclass/npc.hpp | 2 + apps/openmw/mwmechanics/actorutil.hpp | 28 +++++++++ apps/openmw/mwscript/containerextensions.cpp | 14 +++++ apps/openmw/mwworld/class.cpp | 5 ++ apps/openmw/mwworld/class.hpp | 2 + apps/openmw/mwworld/esmstore.cpp | 65 ++++++++++++++++++++ apps/openmw/mwworld/esmstore.hpp | 5 ++ 10 files changed, 133 insertions(+) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 24f25e508..4f411ad81 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -874,6 +874,11 @@ namespace MWClass MWMechanics::setBaseAISetting(id, setting, value); } + void Creature::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const + { + MWMechanics::modifyBaseInventory(actorId, itemId, amount); + } + float Creature::getWalkSpeed(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 9071b9a33..2d7aa5a19 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -132,6 +132,8 @@ namespace MWClass virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; + virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const; + float getWalkSpeed(const MWWorld::Ptr& ptr) const final; float getRunSpeed(const MWWorld::Ptr& ptr) const final; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index edf0709db..d4e40032d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1447,6 +1447,11 @@ namespace MWClass MWMechanics::setBaseAISetting(id, setting, value); } + void Npc::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const + { + MWMechanics::modifyBaseInventory(actorId, itemId, amount); + } + float Npc::getWalkSpeed(const MWWorld::Ptr& ptr) const { const GMST& gmst = getGmst(); diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index d92293acb..d52afcd82 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -167,6 +167,8 @@ namespace MWClass virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; + virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const; + float getWalkSpeed(const MWWorld::Ptr& ptr) const final; float getRunSpeed(const MWWorld::Ptr& ptr) const final; diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp index cb77ef3ea..275a3a814 100644 --- a/apps/openmw/mwmechanics/actorutil.hpp +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MWMECHANICS_ACTORUTIL_H #define OPENMW_MWMECHANICS_ACTORUTIL_H +#include + #include #include @@ -53,8 +55,34 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->createOverrideRecord(copy); } + template + void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) + { + ESM::NPC copy = *MWBase::Environment::get().getWorld()->getStore().get().find(actorId); + for(auto& it : copy.mInventory.mList) + { + if(Misc::StringUtils::ciEqual(it.mItem, itemId)) + { + int sign = it.mCount < 1 ? -1 : 1; + it.mCount = sign * std::max(it.mCount * sign + amount, 0); + MWBase::Environment::get().getWorld()->createOverrideRecord(copy); + return; + } + } + if(amount > 0) + { + ESM::ContItem cont; + cont.mItem = itemId; + cont.mCount = amount; + copy.mInventory.mList.push_back(cont); + MWBase::Environment::get().getWorld()->createOverrideRecord(copy); + } + } + template void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value); template void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value); + template void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount); + template void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount); } #endif diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 7116b1c9f..9ed9204ad 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -60,6 +60,13 @@ namespace MWScript || ::Misc::StringUtils::ciEqual(item, "gold_100")) item = "gold_001"; + // Explicit calls to non-unique actors affect the base record + if(!R::implicit && ptr.getClass().isActor() && MWBase::Environment::get().getWorld()->getStore().getRefCount(ptr.getCellRef().getRefId()) > 1) + { + ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, count); + return; + } + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); // Create a Ptr for the first added item to recover the item name later MWWorld::Ptr itemPtr = *store.add (item, 1, ptr); @@ -147,6 +154,13 @@ namespace MWScript || ::Misc::StringUtils::ciEqual(item, "gold_100")) item = "gold_001"; + // Explicit calls to non-unique actors affect the base record + if(!R::implicit && ptr.getClass().isActor() && MWBase::Environment::get().getWorld()->getStore().getRefCount(ptr.getCellRef().getRefId()) > 1) + { + ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, -count); + return; + } + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); std::string itemName; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index b59532f2a..ad8766d06 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -522,6 +522,11 @@ namespace MWWorld throw std::runtime_error ("class does not have creature stats"); } + void Class::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const + { + throw std::runtime_error ("class does not have an inventory store"); + } + float Class::getWalkSpeed(const Ptr& /*ptr*/) const { return 0; diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index f92dc0373..e82712220 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -363,6 +363,8 @@ namespace MWWorld virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; + virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const; + virtual float getWalkSpeed(const Ptr& ptr) const; virtual float getRunSpeed(const Ptr& ptr) const; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 278b8532e..f6fccba92 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -9,6 +9,45 @@ #include #include +namespace +{ + void readRefs(const ESM::Cell& cell, std::map& refs, std::vector& readers) + { + for (size_t i = 0; i < cell.mContextList.size(); i++) + { + size_t index = cell.mContextList[i].index; + if (readers.size() <= index) + readers.resize(index + 1); + cell.restore(readers[index], i); + ESM::CellRef ref; + ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + bool deleted = false; + while(cell.getNextRef(readers[index], ref, deleted)) + { + if(deleted) + refs.erase(ref.mRefNum); + else if (std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum) == cell.mMovedRefs.end()) + { + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + refs[ref.mRefNum] = ref.mRefID; + } + } + } + for(const auto& it : cell.mLeasedRefs) + { + bool deleted = it.second; + if(deleted) + refs.erase(it.first.mRefNum); + else + { + ESM::CellRef ref = it.first; + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + refs[ref.mRefNum] = ref.mRefID; + } + } + } +} + namespace MWWorld { @@ -146,7 +185,33 @@ void ESMStore::setUp(bool validateRecords) mDialogs.setUp(); if (validateRecords) + { validate(); + countRecords(); + } +} + +void ESMStore::countRecords() +{ + if(!mRefCount.empty()) + return; + std::map refs; + std::vector readers; + for(auto it = mCells.intBegin(); it != mCells.intEnd(); it++) + readRefs(*it, refs, readers); + for(auto it = mCells.extBegin(); it != mCells.extEnd(); it++) + readRefs(*it, refs, readers); + for(const auto& pair : refs) + mRefCount[pair.second]++; +} + +int ESMStore::getRefCount(const std::string& id) const +{ + const std::string lowerId = Misc::StringUtils::lowerCase(id); + auto it = mRefCount.find(lowerId); + if(it == mRefCount.end()) + return 0; + return it->second; } void ESMStore::validate() diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 24364bfb1..b6c78f042 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -70,6 +70,8 @@ namespace MWWorld std::map mIds; std::map mStaticIds; + std::map mRefCount; + std::map mStores; ESM::NPC mPlayerTemplate; @@ -79,6 +81,7 @@ namespace MWWorld /// Validate entries in store after setup void validate(); + void countRecords(); public: /// \todo replace with SharedIterator typedef std::map::const_iterator iterator; @@ -252,6 +255,8 @@ namespace MWWorld // To be called when we are done with dynamic record loading void checkPlayer(); + + int getRefCount(const std::string& id) const; }; template <> From cc6e4ad2155d7c115b25ee77ea9cd302b924e745 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 26 Jul 2020 12:17:06 +0300 Subject: [PATCH 14/28] Fix pickpocketing sneaking stance check --- apps/openmw/mwclass/npc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index edf0709db..8e46b3fc5 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -904,7 +904,7 @@ namespace MWClass } else if (!stats.getAiSequence().isInCombat()) { - if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak) || stats.getKnockedDown()) + if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor)) return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing // Can't talk to werewolves From 9c930e38fab2626b424d2d828a8eb027b205b03b Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 26 Jul 2020 13:02:12 +0300 Subject: [PATCH 15/28] Add option to always allow stealing from KO'd actors (feature #5545) --- CHANGELOG.md | 1 + apps/launcher/advancedpage.cpp | 2 ++ apps/openmw/mwclass/npc.cpp | 15 ++++++++++++++- docs/source/reference/modding/settings/game.rst | 14 ++++++++++++++ files/settings-default.cfg | 3 +++ files/ui/advancedpage.ui | 10 ++++++++++ 6 files changed, 44 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fe149c7a..d4fae3d26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ Feature #5519: Code Patch tab in launcher Feature #5524: Resume failed script execution after reload Feature #5525: Search fields tweaks (utf-8) + Feature #5545: Option to allow stealing from an unconscious NPC during combat Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 07fe8ddd6..2dd7d07c1 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -122,6 +122,7 @@ bool Launcher::AdvancedPage::loadSettings() int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game"); if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2) unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex); + loadSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game"); } // Visuals @@ -215,6 +216,7 @@ void Launcher::AdvancedPage::saveSettings() int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex(); if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game")) mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex); + saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game"); } // Visuals diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index edf0709db..d307f5da6 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -911,6 +911,12 @@ namespace MWClass if (!getNpcStats(ptr).isWerewolf()) return std::shared_ptr(new MWWorld::ActionTalk(ptr)); } + else // In combat + { + const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game"); + if (stealingInCombat && stats.getKnockedDown()) + return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing + } // Tribunal and some mod companions oddly enough must use open action as fallback if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) @@ -1062,7 +1068,14 @@ namespace MWClass if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished()) return true; - return !customData.mNpcStats.getAiSequence().isInCombat(); + if (!customData.mNpcStats.getAiSequence().isInCombat()) + return true; + + const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game"); + if (stealingInCombat && customData.mNpcStats.getKnockedDown()) + return true; + + return false; } MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 072c614d4..5291fb0ed 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -356,3 +356,17 @@ If disabled then the 3 best skills of trainers and the training limits take into If enabled then the 3 best skills of trainers and the training limits are based on the trainer base skills. This setting can be controlled in Advanced tab of the launcher. + +always allow stealing from knocked out actors +--------------------------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +By Bethesda's design, in the latest released version of Morrowind pickpocketing is impossible during combat, +even if the fighting NPC is knocked out. + +This setting allows the player to steal items from fighting NPCs that were knocked out if enabled. + +This setting can be controlled in Advanced tab of the launcher. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index a9777fc42..65e72c177 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -331,6 +331,9 @@ swim upward coef = 0.0 # Make the training skills proposed by a trainer based on its base attribute instead of its modified ones trainers training skills based on base skill = false +# Make stealing items from NPCs that were knocked down possible during combat. +always allow stealing from knocked out actors = false + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index de8e3dfc0..08ffe10ce 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -154,6 +154,16 @@ + + + + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + + + Always allow stealing from knocked out actors + + + From b0c433657790bbfdc2eae32df7e269345019704f Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sun, 26 Jul 2020 22:53:46 +0200 Subject: [PATCH 16/28] update DejaVuFontLicense entry --- AUTHORS.md | 2 +- CMakeLists.txt | 6 ++---- README.md | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 0f0522c44..b6e8c2d17 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -302,4 +302,4 @@ Thanks to Kevin Ryan, for creating the icon used for the Data Files tab of the OpenMW Launcher. Thanks to DejaVu team, -for their DejaVuLGCSansMono fontface, see DejaVu Font License.txt for their license terms. +for their DejaVuLGCSansMono fontface, see DejaVuFontLicense.txt for their license terms. diff --git a/CMakeLists.txt b/CMakeLists.txt index dbe28fd84..f57cf17fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -451,7 +451,7 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF(BUILD_WIZARD) # Install licenses - INSTALL(FILES "files/mygui/DejaVu Font License.txt" DESTINATION "${LICDIR}" ) + INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" ) # Install icon and desktop file INSTALL(FILES "${OpenMW_BINARY_DIR}/org.openmw.launcher.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw") @@ -487,9 +487,7 @@ if(WIN32) INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt") - INSTALL(FILES - "${OpenMW_SOURCE_DIR}/files/mygui/DejaVu Font License.txt" - DESTINATION ".") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/mygui/DejaVuFontLicense.txt" DESTINATION ".") INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/settings-default.cfg" DESTINATION "." CONFIGURATIONS Debug) INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/settings-default.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Debug) diff --git a/README.md b/README.md index 07479933e..6aa0a8515 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set. * IRC: #openmw on irc.freenode.net Font Licenses: -* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVu Font License.txt](https://github.com/OpenMW/openmw/blob/master/files/mygui/DejaVu%20Font%20License.txt) for more information) +* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVuFontLicense.txt](https://github.com/OpenMW/openmw/blob/master/files/mygui/DejaVuFontLicense.txt) for more information) Current Status -------------- From 03f0bd3df6d71c0afbaad5f7e68b3ffab5538b22 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 27 Jul 2020 00:29:35 +0300 Subject: [PATCH 17/28] Make a horrible simple water hack less horrible --- apps/openmw/mwrender/renderingmanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d9739e844..c6ac632e1 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -372,6 +372,7 @@ namespace MWRender mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); + mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); From 65c92b648825c9a5f62205bf01011308ef56e0ea Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 25 Jul 2020 11:03:57 +0200 Subject: [PATCH 18/28] Add alignment="Qt::AlignTop" to files/ui/graphicspage.ui --- files/ui/graphicspage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/ui/graphicspage.ui b/files/ui/graphicspage.ui index cfa2c800c..5ec72611f 100644 --- a/files/ui/graphicspage.ui +++ b/files/ui/graphicspage.ui @@ -11,7 +11,7 @@ - + 0 From 6ad20ec9c782829694214a2f5702b8c985bd31b8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 28 Jul 2020 08:33:28 +0200 Subject: [PATCH 19/28] Mutate base records when adding/removing spells --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwclass/creature.cpp | 14 +- apps/openmw/mwclass/npc.cpp | 39 +-- apps/openmw/mwmechanics/actors.cpp | 4 - apps/openmw/mwmechanics/disease.hpp | 2 +- .../mwmechanics/mechanicsmanagerimp.cpp | 2 +- apps/openmw/mwmechanics/spells.cpp | 260 ++++++++++-------- apps/openmw/mwmechanics/spells.hpp | 50 ++-- apps/openmw/mwworld/esmstore.cpp | 21 ++ apps/openmw/mwworld/esmstore.hpp | 10 + apps/openmw_test_suite/mwworld/test_store.cpp | 6 + components/esm/savedgame.cpp | 2 +- 12 files changed, 231 insertions(+), 181 deletions(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 01d270f82..d8fdf7e33 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -82,7 +82,7 @@ add_openmw_dir (mwclass ) add_openmw_dir (mwmechanics - mechanicsmanagerimp stat creaturestats magiceffects movement actorutil + mechanicsmanagerimp stat creaturestats magiceffects movement actorutil spelllist drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 4f411ad81..15e0fa6ab 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -141,14 +141,9 @@ namespace MWClass data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr)); // spells - for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); - iter!=ref->mBase->mSpells.mList.end(); ++iter) - { - if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) - data->mCreatureStats.getSpells().add (spell); - else /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility - Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << *iter << "' on creature '" << ref->mBase->mId << "'"; - } + bool spellsInitialised = data->mCreatureStats.getSpells().setSpells(ref->mBase->mId); + if (!spellsInitialised) + data->mCreatureStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList); // inventory bool hasInventory = hasInventoryStore(ptr); @@ -781,6 +776,9 @@ namespace MWClass CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); const ESM::CreatureState& creatureState = state.asCreatureState(); customData.mContainerStore->readState (creatureState.mInventory); + bool spellsInitialised = customData.mCreatureStats.getSpells().setSpells(ptr.get()->mBase->mId); + if(spellsInitialised) + customData.mCreatureStats.getSpells().clear(); customData.mCreatureStats.readState (creatureState.mCreatureStats); } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index bc29d0973..0c00d3bd1 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -158,7 +158,7 @@ namespace * * and by adding class, race, specialization bonus. */ - void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr) + void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr, bool spellsInitialised) { const ESM::Class *class_ = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); @@ -235,9 +235,11 @@ namespace for (int i=0; i spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); - for (std::vector::iterator it = spells.begin(); it != spells.end(); ++it) - npcStats.getSpells().add(*it); + if (!spellsInitialised) + { + std::vector spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); + npcStats.getSpells().addAllToInstance(spells); + } } } @@ -311,6 +313,8 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); + bool spellsInitialised = data->mNpcStats.getSpells().setSpells(ref->mBase->mId); + // creature stats int gold=0; if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) @@ -351,7 +355,7 @@ namespace MWClass data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation); autoCalculateAttributes(ref->mBase, data->mNpcStats); - autoCalculateSkills(ref->mBase, data->mNpcStats, ptr); + autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised); data->mNpcStats.setNeedRecalcDynamicStats(true); } @@ -362,14 +366,7 @@ namespace MWClass // race powers const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); - for (std::vector::const_iterator iter (race->mPowers.mList.begin()); - iter!=race->mPowers.mList.end(); ++iter) - { - if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) - data->mNpcStats.getSpells().add (spell); - else - Log(Debug::Warning) << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'"; - } + data->mNpcStats.getSpells().addAllToInstance(race->mPowers.mList); if (!ref->mBase->mFaction.empty()) { @@ -390,17 +387,8 @@ namespace MWClass data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); // spells - for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); - iter!=ref->mBase->mSpells.mList.end(); ++iter) - { - if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) - data->mNpcStats.getSpells().add (spell); - else - { - /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility - Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << *iter << "' on NPC '" << ref->mBase->mId << "'"; - } - } + if (!spellsInitialised) + data->mNpcStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList); // inventory // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items @@ -1326,6 +1314,9 @@ namespace MWClass const ESM::NpcState& npcState = state.asNpcState(); customData.mInventoryStore.readState (npcState.mInventory); customData.mNpcStats.readState (npcState.mNpcStats); + bool spellsInitialised = customData.mNpcStats.getSpells().setSpells(ptr.get()->mBase->mId); + if(spellsInitialised) + customData.mNpcStats.getSpells().clear(); customData.mNpcStats.readState (npcState.mCreatureStats); } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 93b0e0a89..b91b01e4a 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1973,10 +1973,6 @@ namespace MWMechanics // One case where we need this is to make sure bound items are removed upon death stats.modifyMagicEffects(MWMechanics::MagicEffects()); stats.getActiveSpells().clear(); - - if (!isPlayer) - stats.getSpells().clear(); - // Make sure spell effects are removed purgeSpellEffects(stats.getActorId()); diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 0c5706775..7933c927e 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -40,7 +40,7 @@ namespace MWMechanics continue; float resist = 0.f; - if (spells.hasCorprusEffect(spell)) + if (Spells::hasCorprusEffect(spell)) resist = 1.f - 0.01f * (actorEffects.get(ESM::MagicEffect::ResistCorprusDisease).getMagnitude() - actorEffects.get(ESM::MagicEffect::WeaknessToCorprusDisease).getMagnitude()); else if (spell->mData.mType == ESM::Spell::ST_Disease) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index fd8902b37..466a3bcc8 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -83,7 +83,7 @@ namespace MWMechanics // reset creatureStats.setLevel(player->mNpdt.mLevel); - creatureStats.getSpells().clear(); + creatureStats.getSpells().clear(true); creatureStats.modifyMagicEffects(MagicEffects()); for (int i=0; i<27; ++i) diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index ae7454f19..4bffcab9b 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -1,8 +1,10 @@ #include "spells.hpp" +#include #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -22,46 +24,41 @@ namespace MWMechanics { } - Spells::TIterator Spells::begin() const + std::map::const_iterator Spells::begin() const { return mSpells.begin(); } - Spells::TIterator Spells::end() const + std::map::const_iterator Spells::end() const { return mSpells.end(); } - const ESM::Spell* Spells::getSpell(const std::string& id) const - { - return MWBase::Environment::get().getWorld()->getStore().get().find(id); - } - void Spells::rebuildEffects() const { mEffects = MagicEffects(); mSourcedEffects.clear(); - for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) + for (const auto& iter : mSpells) { - const ESM::Spell *spell = iter->first; + const ESM::Spell *spell = iter.first; if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse) { int i=0; - for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) + for (const auto& effect : spell->mEffects.mList) { - if (iter->second.mPurgedEffects.find(i) != iter->second.mPurgedEffects.end()) + if (iter.second.mPurgedEffects.find(i) != iter.second.mPurgedEffects.end()) continue; // effect was purged float random = 1.f; - if (iter->second.mEffectRands.find(i) != iter->second.mEffectRands.end()) - random = iter->second.mEffectRands.at(i); + if (iter.second.mEffectRands.find(i) != iter.second.mEffectRands.end()) + random = iter.second.mEffectRands.at(i); - float magnitude = it->mMagnMin + (it->mMagnMax - it->mMagnMin) * random; - mEffects.add (*it, magnitude); - mSourcedEffects[spell].add(MWMechanics::EffectKey(*it), magnitude); + float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * random; + mEffects.add (effect, magnitude); + mSourcedEffects[spell].add(MWMechanics::EffectKey(effect), magnitude); ++i; } @@ -71,7 +68,7 @@ namespace MWMechanics bool Spells::hasSpell(const std::string &spell) const { - return hasSpell(getSpell(spell)); + return hasSpell(SpellList::getSpell(spell)); } bool Spells::hasSpell(const ESM::Spell *spell) const @@ -80,6 +77,16 @@ namespace MWMechanics } void Spells::add (const ESM::Spell* spell) + { + mSpellList->add(spell); + } + + void Spells::add (const std::string& spellId) + { + add(SpellList::getSpell(spellId)); + } + + void Spells::addSpell(const ESM::Spell* spell) { if (mSpells.find (spell)==mSpells.end()) { @@ -106,26 +113,26 @@ namespace MWMechanics } } - void Spells::add (const std::string& spellId) - { - add(getSpell(spellId)); - } - void Spells::remove (const std::string& spellId) { - const ESM::Spell* spell = getSpell(spellId); - TContainer::iterator iter = mSpells.find (spell); - - if (iter!=mSpells.end()) - { - mSpells.erase (iter); - mSpellsChanged = true; - } + const auto spell = SpellList::getSpell(spellId); + removeSpell(spell); + mSpellList->remove(spell); if (spellId==mSelectedSpell) mSelectedSpell.clear(); } + void Spells::removeSpell(const ESM::Spell* spell) + { + const auto it = mSpells.find(spell); + if(it != mSpells.end()) + { + mSpells.erase(it); + mSpellsChanged = true; + } + } + MagicEffects Spells::getMagicEffects() const { if (mSpellsChanged) { @@ -135,12 +142,19 @@ namespace MWMechanics return mEffects; } - void Spells::clear() + void Spells::removeAllSpells() { mSpells.clear(); mSpellsChanged = true; } + void Spells::clear(bool modifyBase) + { + removeAllSpells(); + if(modifyBase) + mSpellList->clear(); + } + void Spells::setSelectedSpell (const std::string& spellId) { mSelectedSpell = spellId; @@ -166,101 +180,78 @@ namespace MWMechanics return false; } - bool Spells::hasCommonDisease() const + bool Spells::hasDisease(const ESM::Spell::SpellType type) const { - for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) + for (const auto& iter : mSpells) { - const ESM::Spell *spell = iter->first; - if (spell->mData.mType == ESM::Spell::ST_Disease) + const ESM::Spell *spell = iter.first; + if (spell->mData.mType == type) return true; } return false; } + bool Spells::hasCommonDisease() const + { + return hasDisease(ESM::Spell::ST_Disease); + } + bool Spells::hasBlightDisease() const { - for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) + return hasDisease(ESM::Spell::ST_Blight); + } + + void Spells::purge(const SpellFilter& filter) + { + std::vector purged; + for (auto iter = mSpells.begin(); iter!=mSpells.end();) { const ESM::Spell *spell = iter->first; - if (spell->mData.mType == ESM::Spell::ST_Blight) - return true; + if (filter(spell)) + { + mSpells.erase(iter++); + purged.push_back(spell->mId); + mSpellsChanged = true; + } + else + ++iter; } - - return false; + if(!purged.empty()) + mSpellList->removeAll(purged); } void Spells::purgeCommonDisease() { - for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) - { - const ESM::Spell *spell = iter->first; - if (spell->mData.mType == ESM::Spell::ST_Disease) - { - mSpells.erase(iter++); - mSpellsChanged = true; - } - else - ++iter; - } + purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Disease; }); } void Spells::purgeBlightDisease() { - for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) - { - const ESM::Spell *spell = iter->first; - if (spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell)) - { - mSpells.erase(iter++); - mSpellsChanged = true; - } - else - ++iter; - } + purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell); }); } void Spells::purgeCorprusDisease() { - for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) - { - const ESM::Spell *spell = iter->first; - if (hasCorprusEffect(spell)) - { - mSpells.erase(iter++); - mSpellsChanged = true; - } - else - ++iter; - } + purge(&hasCorprusEffect); } void Spells::purgeCurses() { - for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) - { - const ESM::Spell *spell = iter->first; - if (spell->mData.mType == ESM::Spell::ST_Curse) - { - mSpells.erase(iter++); - mSpellsChanged = true; - } - else - ++iter; - } + purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Curse; }); } void Spells::removeEffects(const std::string &id) { if (isSpellActive(id)) { - for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell) + for (auto& spell : mSpells) { - if (spell->first == getSpell(id)) + if (spell.first == SpellList::getSpell(id)) { - for (long unsigned int i = 0; i != spell->first->mEffects.mList.size(); i++) + for (long unsigned int i = 0; i != spell.first->mEffects.mList.size(); i++) { - spell->second.mPurgedEffects.insert(i); + spell.second.mPurgedEffects.insert(i); } } } @@ -276,23 +267,21 @@ namespace MWMechanics mSpellsChanged = false; } - for (std::map::const_iterator it = mSourcedEffects.begin(); - it != mSourcedEffects.end(); ++it) + for (const auto& it : mSourcedEffects) { - const ESM::Spell * spell = it->first; - for (MagicEffects::Collection::const_iterator effectIt = it->second.begin(); - effectIt != it->second.end(); ++effectIt) + const ESM::Spell * spell = it.first; + for (const auto& effectIt : it.second) { - visitor.visit(effectIt->first, spell->mName, spell->mId, -1, effectIt->second.getMagnitude()); + visitor.visit(effectIt.first, spell->mName, spell->mId, -1, effectIt.second.getMagnitude()); } } } bool Spells::hasCorprusEffect(const ESM::Spell *spell) { - for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) + for (const auto& effectIt : spell->mEffects.mList) { - if (effectIt->mEffectID == ESM::MagicEffect::Corprus) + if (effectIt.mEffectID == ESM::MagicEffect::Corprus) { return true; } @@ -302,14 +291,14 @@ namespace MWMechanics void Spells::purgeEffect(int effectId) { - for (TContainer::iterator spellIt = mSpells.begin(); spellIt != mSpells.end(); ++spellIt) + for (auto& spellIt : mSpells) { int i = 0; - for (std::vector::const_iterator effectIt = spellIt->first->mEffects.mList.begin(); effectIt != spellIt->first->mEffects.mList.end(); ++effectIt) + for (auto& effectIt : spellIt.first->mEffects.mList) { - if (effectIt->mEffectID == effectId) + if (effectIt.mEffectID == effectId) { - spellIt->second.mPurgedEffects.insert(i); + spellIt.second.mPurgedEffects.insert(i); mSpellsChanged = true; } ++i; @@ -319,15 +308,15 @@ namespace MWMechanics void Spells::purgeEffect(int effectId, const std::string & sourceId) { - const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().find(sourceId); - TContainer::iterator spellIt = mSpells.find(spell); + const ESM::Spell * spell = SpellList::getSpell(sourceId); + auto spellIt = mSpells.find(spell); if (spellIt == mSpells.end()) return; int i = 0; - for (std::vector::const_iterator effectIt = spellIt->first->mEffects.mList.begin(); effectIt != spellIt->first->mEffects.mList.end(); ++effectIt) + for (auto& effectIt : spellIt->first->mEffects.mList) { - if (effectIt->mEffectID == effectId) + if (effectIt.mEffectID == effectId) { spellIt->second.mPurgedEffects.insert(i); mSpellsChanged = true; @@ -338,11 +327,8 @@ namespace MWMechanics bool Spells::canUsePower(const ESM::Spell* spell) const { - std::map::const_iterator it = mUsedPowers.find(spell); - if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp()) - return true; - else - return false; + const auto it = mUsedPowers.find(spell); + return it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp(); } void Spells::usePower(const ESM::Spell* spell) @@ -352,6 +338,8 @@ namespace MWMechanics void Spells::readState(const ESM::SpellState &state, CreatureStats* creatureStats) { + const auto& baseSpells = mSpellList->getSpells(); + for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) { // Discard spells that are no longer available due to changed content files @@ -365,6 +353,13 @@ namespace MWMechanics mSelectedSpell = it->first; } } + // Add spells from the base record + for(const std::string& id : baseSpells) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); + if(spell) + addSpell(spell); + } for (std::map::const_iterator it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it) { @@ -436,17 +431,50 @@ namespace MWMechanics void Spells::writeState(ESM::SpellState &state) const { - for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) + const auto& baseSpells = mSpellList->getSpells(); + for (const auto& it : mSpells) { - ESM::SpellState::SpellParams params; - params.mEffectRands = it->second.mEffectRands; - params.mPurgedEffects = it->second.mPurgedEffects; - state.mSpells.insert(std::make_pair(it->first->mId, params)); + //Don't save spells stored in the base record + if(std::find(baseSpells.begin(), baseSpells.end(), it.first->mId) == baseSpells.end()) + { + ESM::SpellState::SpellParams params; + params.mEffectRands = it.second.mEffectRands; + params.mPurgedEffects = it.second.mPurgedEffects; + state.mSpells.insert(std::make_pair(it.first->mId, params)); + } } state.mSelectedSpell = mSelectedSpell; - for (std::map::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) - state.mUsedPowers[it->first->mId] = it->second.toEsm(); + for (const auto& it : mUsedPowers) + state.mUsedPowers[it.first->mId] = it.second.toEsm(); + } + + bool Spells::setSpells(const std::string& actorId) + { + bool result; + std::tie(mSpellList, result) = MWBase::Environment::get().getWorld()->getStore().getSpellList(actorId); + mSpellList->addListener(this); + for(const auto& id : mSpellList->getSpells()) + addSpell(SpellList::getSpell(id)); + return result; + } + + void Spells::addAllToInstance(const std::vector& spells) + { + for(const std::string& id : spells) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); + if(spell) + addSpell(spell); + else + Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << id << "'"; + } + } + + Spells::~Spells() + { + if(mSpellList) + mSpellList->removeListener(this); } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index a4a599f8b..2f4049d2e 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -1,22 +1,19 @@ #ifndef GAME_MWMECHANICS_SPELLS_H #define GAME_MWMECHANICS_SPELLS_H +#include #include #include #include +#include -#include - -#include "../mwworld/ptr.hpp" #include "../mwworld/timestamp.hpp" #include "magiceffects.hpp" - +#include "spelllist.hpp" namespace ESM { - struct Spell; - struct SpellState; } @@ -32,37 +29,36 @@ namespace MWMechanics /// diseases. It also keeps track of used powers (which can only be used every 24h). class Spells { - public: - - typedef const ESM::Spell* SpellKey; - struct SpellParams - { - std::map mEffectRands; // - std::set mPurgedEffects; // indices of purged effects - }; - - typedef std::map TContainer; - typedef TContainer::const_iterator TIterator; - - private: - TContainer mSpells; + std::shared_ptr mSpellList; + std::map mSpells; // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) std::string mSelectedSpell; - std::map mUsedPowers; + std::map mUsedPowers; mutable bool mSpellsChanged; mutable MagicEffects mEffects; - mutable std::map mSourcedEffects; + mutable std::map mSourcedEffects; void rebuildEffects() const; - /// Get spell from ID, throws exception if not found - const ESM::Spell* getSpell(const std::string& id) const; + bool hasDisease(const ESM::Spell::SpellType type) const; + using SpellFilter = bool (*)(const ESM::Spell*); + void purge(const SpellFilter& filter); + + void addSpell(const ESM::Spell* spell); + void removeSpell(const ESM::Spell* spell); + void removeAllSpells(); + + friend class SpellList; public: + using TIterator = std::map::const_iterator; + Spells(); + ~Spells(); + static bool hasCorprusEffect(const ESM::Spell *spell); void purgeEffect(int effectId); @@ -96,7 +92,7 @@ namespace MWMechanics MagicEffects getMagicEffects() const; ///< Return sum of magic effects resulting from abilities, blights, deseases and curses. - void clear(); + void clear(bool modifyBase = false); ///< Remove all spells of al types. void setSelectedSpell (const std::string& spellId); @@ -118,6 +114,10 @@ namespace MWMechanics void readState (const ESM::SpellState& state, CreatureStats* creatureStats); void writeState (ESM::SpellState& state) const; + + bool setSpells(const std::string& id); + + void addAllToInstance(const std::vector& spells); }; } diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index f6fccba92..aea9a5e4f 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -9,6 +9,8 @@ #include #include +#include "../mwmechanics/spelllist.hpp" + namespace { void readRefs(const ESM::Cell& cell, std::map& refs, std::vector& readers) @@ -409,4 +411,23 @@ void ESMStore::validate() throw std::runtime_error ("Invalid player record (race or class unavailable"); } + std::pair, bool> ESMStore::getSpellList(const std::string& originalId) const + { + const std::string id = Misc::StringUtils::lowerCase(originalId); + auto result = mSpellListCache.find(id); + std::shared_ptr ptr; + if (result != mSpellListCache.end()) + ptr = result->second.lock(); + if (!ptr) + { + int type = find(id); + ptr = std::make_shared(id, type); + if (result != mSpellListCache.end()) + result->second = ptr; + else + mSpellListCache.insert({id, ptr}); + return {ptr, false}; + } + return {ptr, true}; + } } // end namespace diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index b6c78f042..ceb05ca80 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_MWWORLD_ESMSTORE_H #define OPENMW_MWWORLD_ESMSTORE_H +#include #include #include @@ -12,6 +13,11 @@ namespace Loading class Listener; } +namespace MWMechanics +{ + class SpellList; +} + namespace MWWorld { class ESMStore @@ -78,6 +84,8 @@ namespace MWWorld unsigned int mDynamicCount; + mutable std::map > mSpellListCache; + /// Validate entries in store after setup void validate(); @@ -257,6 +265,8 @@ namespace MWWorld void checkPlayer(); int getRefCount(const std::string& id) const; + + std::pair, bool> getSpellList(const std::string& id) const; }; template <> diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index 63e4bd6af..60f60adb3 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -8,6 +8,12 @@ #include #include "apps/openmw/mwworld/esmstore.hpp" +#include "apps/openmw/mwmechanics/spelllist.hpp" + +namespace MWMechanics +{ + SpellList::SpellList(const std::string& id, int type) : mId(id), mType(type) {} +} static Loading::Listener dummyListener; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 76695dbe8..4b0529703 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -4,7 +4,7 @@ #include "esmwriter.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 12; +int ESM::SavedGame::sCurrentFormat = 13; void ESM::SavedGame::load (ESMReader &esm) { From e27858cfabf2e8489787e93a32595cc63b16730b Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 28 Jul 2020 18:24:09 +0200 Subject: [PATCH 20/28] these were supposed to be included --- apps/openmw/mwmechanics/spelllist.cpp | 175 ++++++++++++++++++++++++++ apps/openmw/mwmechanics/spelllist.hpp | 60 +++++++++ 2 files changed, 235 insertions(+) create mode 100644 apps/openmw/mwmechanics/spelllist.cpp create mode 100644 apps/openmw/mwmechanics/spelllist.hpp diff --git a/apps/openmw/mwmechanics/spelllist.cpp b/apps/openmw/mwmechanics/spelllist.cpp new file mode 100644 index 000000000..891b28619 --- /dev/null +++ b/apps/openmw/mwmechanics/spelllist.cpp @@ -0,0 +1,175 @@ +#include "spelllist.hpp" + +#include + +#include +#include + +#include "spells.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/esmstore.hpp" + +namespace +{ + template + const std::vector getSpellList(const std::string& id) + { + return MWBase::Environment::get().getWorld()->getStore().get().find(id)->mSpells.mList; + } + + template + bool withBaseRecord(const std::string& id, const std::function&)>& function) + { + T copy = *MWBase::Environment::get().getWorld()->getStore().get().find(id); + bool changed = function(copy.mSpells.mList); + if(changed) + MWBase::Environment::get().getWorld()->createOverrideRecord(copy); + return changed; + } +} + +namespace MWMechanics +{ + SpellList::SpellList(const std::string& id, int type) : mId(id), mType(type) {} + + bool SpellList::withBaseRecord(const std::function&)>& function) + { + switch(mType) + { + case ESM::REC_CREA: + return ::withBaseRecord(mId, function); + case ESM::REC_NPC_: + return ::withBaseRecord(mId, function); + default: + throw std::logic_error("failed to update base record for " + mId); + } + } + + const std::vector SpellList::getSpells() const + { + switch(mType) + { + case ESM::REC_CREA: + return getSpellList(mId); + case ESM::REC_NPC_: + return getSpellList(mId); + default: + throw std::logic_error("failed to get spell list for " + mId); + } + } + + const ESM::Spell* SpellList::getSpell(const std::string& id) + { + return MWBase::Environment::get().getWorld()->getStore().get().find(id); + } + + void SpellList::add (const ESM::Spell* spell) + { + auto& id = spell->mId; + bool changed = withBaseRecord([&] (auto& spells) + { + for(auto it : spells) + { + if(Misc::StringUtils::ciEqual(id, it)) + return false; + } + spells.push_back(id); + return true; + }); + if(changed) + { + for(auto listener : mListeners) + listener->addSpell(spell); + } + } + + void SpellList::remove (const ESM::Spell* spell) + { + auto& id = spell->mId; + bool changed = withBaseRecord([&] (auto& spells) + { + for(auto it = spells.begin(); it != spells.end(); it++) + { + if(Misc::StringUtils::ciEqual(id, *it)) + { + spells.erase(it); + return true; + } + } + return false; + }); + if(changed) + { + for(auto listener : mListeners) + listener->removeSpell(spell); + } + } + + void SpellList::removeAll (const std::vector& ids) + { + bool changed = withBaseRecord([&] (auto& spells) + { + const auto it = std::remove_if(spells.begin(), spells.end(), [&] (const auto& spell) + { + const auto isSpell = [&] (const auto& id) { return Misc::StringUtils::ciEqual(spell, id); }; + return ids.end() != std::find_if(ids.begin(), ids.end(), isSpell); + }); + if (it == spells.end()) + return false; + spells.erase(it, spells.end()); + return true; + }); + if(changed) + { + for(auto listener : mListeners) + { + for(auto& id : ids) + { + const auto spell = getSpell(id); + listener->removeSpell(spell); + } + } + } + } + + void SpellList::clear() + { + bool changed = withBaseRecord([] (auto& spells) + { + if(spells.empty()) + return false; + spells.clear(); + return true; + }); + if(changed) + { + for(auto listener : mListeners) + listener->removeAllSpells(); + } + } + + void SpellList::addListener(Spells* spells) + { + for(const auto ptr : mListeners) + { + if(ptr == spells) + return; + } + mListeners.push_back(spells); + } + + void SpellList::removeListener(Spells* spells) + { + for(auto it = mListeners.begin(); it != mListeners.end(); it++) + { + if(*it == spells) + { + mListeners.erase(it); + break; + } + } + } +} diff --git a/apps/openmw/mwmechanics/spelllist.hpp b/apps/openmw/mwmechanics/spelllist.hpp new file mode 100644 index 000000000..87420082f --- /dev/null +++ b/apps/openmw/mwmechanics/spelllist.hpp @@ -0,0 +1,60 @@ +#ifndef GAME_MWMECHANICS_SPELLLIST_H +#define GAME_MWMECHANICS_SPELLLIST_H + +#include +#include +#include +#include +#include + +#include + +#include "magiceffects.hpp" + +namespace ESM +{ + struct SpellState; +} + +namespace MWMechanics +{ + struct SpellParams + { + std::map mEffectRands; // + std::set mPurgedEffects; // indices of purged effects + }; + + class Spells; + + class SpellList + { + const std::string mId; + const int mType; + std::vector mListeners; + + bool withBaseRecord(const std::function&)>& function); + public: + SpellList(const std::string& id, int type); + + /// Get spell from ID, throws exception if not found + static const ESM::Spell* getSpell(const std::string& id); + + void add (const ESM::Spell* spell); + ///< Adding a spell that is already listed in *this is a no-op. + + void remove (const ESM::Spell* spell); + + void removeAll(const std::vector& spells); + + void clear(); + ///< Remove all spells of all types. + + void addListener(Spells* spells); + + void removeListener(Spells* spells); + + const std::vector getSpells() const; + }; +} + +#endif From 12ee42c6f3b636da958d7039024cdf728288af3c Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Tue, 28 Jul 2020 20:50:49 +0000 Subject: [PATCH 21/28] Update mLastTopic before executing scripts. In case the script contains ClearInfoActor it erase not the correct topic. Other correction not linked with the bug: - reset mActorKnownTopicsFlag at the same time as mActorKnownTopics - don't call updateTopics in addResponse, it is already handled by the response callback --- CHANGELOG.md | 1 + apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 6 ++++-- apps/openmw/mwgui/dialogue.cpp | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4fae3d26..321eb3c0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Bug #5502: Dead zone for analogue stick movement is too small Bug #5507: Sound volume is not clamped on ingame settings update Bug #5531: Actors flee using current rotation by axis x + Bug #5548: Certain exhausted topics can be highlighted again even though there's no new dialogue Feature #390: 3rd person look "over the shoulder" Feature #2386: Distant Statics in the form of Object Paging Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 3e0cbbad2..e3f1796a4 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -121,6 +121,7 @@ namespace MWDialogue mTalkedTo = creatureStats.hasTalkedToPlayer(); mActorKnownTopics.clear(); + mActorKnownTopicsFlag.clear(); //greeting const MWWorld::Store &dialogs = @@ -300,11 +301,11 @@ namespace MWDialogue } } + mLastTopic = topic; + executeScript (info->mResultScript, mActor); parseText (info->mResponse); - - mLastTopic = topic; } } @@ -323,6 +324,7 @@ namespace MWDialogue updateGlobals(); mActorKnownTopics.clear(); + mActorKnownTopicsFlag.clear(); const auto& dialogs = MWBase::Environment::get().getWorld()->getStore().get(); diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index a674dd36e..2527da374 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -678,7 +678,6 @@ namespace MWGui { mHistoryContents.push_back(new Response(text, title, needMargin)); updateHistory(); - updateTopics(); } void DialogueWindow::addMessageBox(const std::string& text) From b39f35d805c975334d80f4c04282467bc0dff444 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 29 Jul 2020 18:43:56 +0200 Subject: [PATCH 22/28] inherit variables --- apps/openmw/mwscript/globalscripts.cpp | 9 +++++++++ apps/openmw/mwscript/globalscripts.hpp | 2 ++ apps/openmw/mwscript/locals.cpp | 27 ++++++++++++++++++-------- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index a7865f0ae..41f153b28 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -299,6 +299,15 @@ namespace MWScript return iter->second->mLocals; } + const Locals* GlobalScripts::getLocalsIfPresent (const std::string& name) const + { + std::string name2 = ::Misc::StringUtils::lowerCase (name); + auto iter = mScripts.find (name2); + if (iter==mScripts.end()) + return nullptr; + return &iter->second->mLocals; + } + void GlobalScripts::updatePtrs(const MWWorld::Ptr& base, const MWWorld::Ptr& updated) { MatchPtrVisitor visitor(base); diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index 36d89a7eb..c5c5a9a45 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -82,6 +82,8 @@ namespace MWScript ///< If the script \a name has not been added as a global script yet, it is added /// automatically, but is not set to running state. + const Locals* getLocalsIfPresent (const std::string& name) const; + void updatePtrs(const MWWorld::Ptr& base, const MWWorld::Ptr& updated); ///< Update the Ptrs stored in mTarget. Should be called after the reference has been moved to a new cell. }; diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index 699200590..381d73ec8 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -1,4 +1,5 @@ #include "locals.hpp" +#include "globalscripts.hpp" #include #include @@ -33,15 +34,25 @@ namespace MWScript if (mInitialised) return false; - const Compiler::Locals& locals = - MWBase::Environment::get().getScriptManager()->getLocals (script.mId); + const Locals* global = MWBase::Environment::get().getScriptManager()->getGlobalScripts().getLocalsIfPresent(script.mId); + if(global) + { + mShorts = global->mShorts; + mLongs = global->mLongs; + mFloats = global->mFloats; + } + else + { + const Compiler::Locals& locals = + MWBase::Environment::get().getScriptManager()->getLocals (script.mId); - mShorts.clear(); - mShorts.resize (locals.get ('s').size(), 0); - mLongs.clear(); - mLongs.resize (locals.get ('l').size(), 0); - mFloats.clear(); - mFloats.resize (locals.get ('f').size(), 0); + mShorts.clear(); + mShorts.resize (locals.get ('s').size(), 0); + mLongs.clear(); + mLongs.resize (locals.get ('l').size(), 0); + mFloats.clear(); + mFloats.resize (locals.get ('f').size(), 0); + } mInitialised = true; return true; From 3d31d21bc2376b786eb05c99c3e594264f4593a1 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 29 Jul 2020 20:42:29 +0300 Subject: [PATCH 23/28] Don't encapsulate NIF transformation changes Currently that causes issues --- components/nifosg/controller.cpp | 47 ++++++++++++++++++++------- components/nifosg/matrixtransform.cpp | 33 ------------------- components/nifosg/matrixtransform.hpp | 13 -------- 3 files changed, 36 insertions(+), 57 deletions(-) diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 203951edd..a4db2cba3 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -119,23 +119,48 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) if (hasInput()) { NifOsg::MatrixTransform* trans = static_cast(node); + osg::Matrix mat = trans->getMatrix(); float time = getInputValue(nv); - if (!mRotations.empty()) - trans->updateRotation(mRotations.interpKey(time)); + Nif::Matrix3& rot = trans->mRotationScale; + + bool setRot = false; + if(!mRotations.empty()) + { + mat.setRotate(mRotations.interpKey(time)); + setRot = true; + } else if (!mXRotations.empty() || !mYRotations.empty() || !mZRotations.empty()) - trans->updateRotation(getXYZRotation(time)); - else // no rotation specified, use the previous value - trans->applyCurrentRotation(); + { + mat.setRotate(getXYZRotation(time)); + setRot = true; + } + else + { + // no rotation specified, use the previous value + for (int i=0;i<3;++i) + for (int j=0;j<3;++j) + mat(j,i) = rot.mValues[i][j]; // NB column/row major difference + } - if (!mScales.empty()) - trans->updateScale(mScales.interpKey(time)); - else // no scale specified, use the previous value - trans->applyCurrentScale(); + if (setRot) // copy the new values back + for (int i=0;i<3;++i) + for (int j=0;j<3;++j) + rot.mValues[i][j] = mat(j,i); // NB column/row major difference - if (!mTranslations.empty()) - trans->setTranslation(mTranslations.interpKey(time)); + float& scale = trans->mScale; + if(!mScales.empty()) + scale = mScales.interpKey(time); + + for (int i=0;i<3;++i) + for (int j=0;j<3;++j) + mat(i,j) *= scale; + + if(!mTranslations.empty()) + mat.setTrans(mTranslations.interpKey(time)); + + trans->setMatrix(mat); } traverse(node, nv); diff --git a/components/nifosg/matrixtransform.cpp b/components/nifosg/matrixtransform.cpp index 72e12ecf8..bc461b9c1 100644 --- a/components/nifosg/matrixtransform.cpp +++ b/components/nifosg/matrixtransform.cpp @@ -20,37 +20,4 @@ namespace NifOsg , mRotationScale(copy.mRotationScale) { } - - void MatrixTransform::applyCurrentRotation() - { - for (int i = 0; i < 3; ++i) - for (int j = 0; j < 3; ++j) - _matrix(j,i) = mRotationScale.mValues[i][j]; // NB column/row major difference - } - - void MatrixTransform::applyCurrentScale() - { - for (int i = 0; i < 3; ++i) - for (int j = 0; j < 3; ++j) - _matrix(i,j) *= mScale; - } - - void MatrixTransform::updateRotation(const osg::Quat& rotation) - { - _matrix.setRotate(rotation); - for (int i = 0; i < 3; ++i) - for (int j = 0; j < 3; ++j) - mRotationScale.mValues[i][j] = _matrix(j,i); // NB column/row major difference - } - - void MatrixTransform::updateScale(const float scale) - { - mScale = scale; - applyCurrentScale(); - } - - void MatrixTransform::setTranslation(const osg::Vec3f& translation) - { - _matrix.setTrans(translation); - } } diff --git a/components/nifosg/matrixtransform.hpp b/components/nifosg/matrixtransform.hpp index ac2fbb57a..975f71c62 100644 --- a/components/nifosg/matrixtransform.hpp +++ b/components/nifosg/matrixtransform.hpp @@ -17,19 +17,6 @@ namespace NifOsg META_Node(NifOsg, MatrixTransform) - // Apply the current NIF rotation or scale to OSG matrix. - void applyCurrentRotation(); - void applyCurrentScale(); - - // Apply the given rotation to OSG matrix directly and update NIF rotation matrix. - void updateRotation(const osg::Quat& rotation); - // Update current NIF scale and apply it to OSG matrix. - void updateScale(const float scale); - - // Apply the given translation to OSG matrix. - void setTranslation(const osg::Vec3f& translation); - - private: // Hack: account for Transform differences between OSG and NIFs. // OSG uses a 4x4 matrix, NIF's use a 3x3 rotationScale, float scale, and vec3 position. // Decomposing the original components from the 4x4 matrix isn't possible, which causes From 9f349e8d9109dd55f4f1d79cf64a29f46aba5b36 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 30 Jul 2020 20:57:25 +0400 Subject: [PATCH 24/28] Use more C++11 in the scripting system code --- apps/openmw/mwscript/interpretercontext.cpp | 20 +++++++++----------- apps/openmw/mwscript/miscextensions.cpp | 8 ++++---- apps/openmw/mwscript/scriptmanagerimp.cpp | 11 ++++++----- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 62febb33d..f3895c8b6 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -225,15 +225,13 @@ namespace MWScript std::vector InterpreterContext::getGlobals() const { - std::vector ids; - const MWWorld::Store& globals = MWBase::Environment::get().getWorld()->getStore().get(); - for (MWWorld::Store::iterator iter = globals.begin(); iter!=globals.end(); - ++iter) + std::vector ids; + for (auto& globalVariable : globals) { - ids.push_back (iter->mId); + ids.emplace_back(globalVariable.mId); } return ids; @@ -245,22 +243,22 @@ namespace MWScript return world->getGlobalVariableType(name); } - std::string InterpreterContext::getActionBinding(const std::string& action) const + std::string InterpreterContext::getActionBinding(const std::string& targetAction) const { MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); std::vector actions = input->getActionKeySorting (); - for (std::vector::const_iterator it = actions.begin(); it != actions.end(); ++it) + for (const int action : actions) { - std::string desc = input->getActionDescription (*it); + std::string desc = input->getActionDescription (action); if(desc == "") continue; - if(desc == action) + if(desc == targetAction) { if(input->joystickLastUsed()) - return input->getActionControllerBindingName(*it); + return input->getActionControllerBindingName(action); else - return input->getActionKeyBindingName (*it); + return input->getActionKeyBindingName(action); } } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 0dd0287a7..cbacd62df 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -45,9 +45,9 @@ namespace void addToLevList(ESM::LevelledListBase* list, const std::string& itemId, int level) { - for (std::vector::iterator it = list->mList.begin(); it != list->mList.end(); ++it) + for (auto& levelItem : list->mList) { - if (it->mLevel == level && itemId == it->mId) + if (levelItem.mLevel == level && itemId == levelItem.mId) return; } @@ -563,9 +563,9 @@ namespace MWScript effects += store.getMagicEffects(); } - for (MWMechanics::MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) + for (const auto& effect : effects) { - if (it->first.mId == key && it->second.getModifier() > 0) + if (effect.first.mId == key && effect.second.getModifier() > 0) { runtime.push(1); return; diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 0077b7cd0..8ff768c2d 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -151,16 +151,17 @@ namespace MWScript const MWWorld::Store& scripts = mStore.get(); - for (MWWorld::Store::iterator iter = scripts.begin(); - iter != scripts.end(); ++iter) + for (auto& script : mStore.get()) + { if (!std::binary_search (mScriptBlacklist.begin(), mScriptBlacklist.end(), - Misc::StringUtils::lowerCase (iter->mId))) + Misc::StringUtils::lowerCase(script.mId))) { ++count; - if (compile (iter->mId)) + if (compile(script.mId)) ++success; } + } return std::make_pair (count, success); } @@ -195,7 +196,7 @@ namespace MWScript scanner.scan (parser); std::map::iterator iter = - mOtherLocals.insert (std::make_pair (name2, locals)).first; + mOtherLocals.emplace(name2, locals).first; return iter->second; } From f5638fec1e9af682a27cd559597c7472e500ad4c Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 31 Jul 2020 19:08:49 +0200 Subject: [PATCH 25/28] add a changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 321eb3c0c..6974fd46c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Bug #3676: NiParticleColorModifier isn't applied properly Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects Bug #4021: Attributes and skills are not stored as floats + Bug #4055: Local scripts don't inherit variables from their base record Bug #4623: Corprus implementation is incorrect Bug #4764: Data race in osg ParticleSystem Bug #4774: Guards are ignorant of an invisible player that tries to attack them From 58e0b34adce8dc4caf4e0c59deb5e56bc7ffaec5 Mon Sep 17 00:00:00 2001 From: descawed Date: Mon, 3 Aug 2020 22:59:08 +0000 Subject: [PATCH 26/28] Verify certificates when downloading dependencies --- CI/before_script.msvc.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index dc6eb7e44..616caf3fa 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -258,10 +258,10 @@ download() { if [ -z $VERBOSE ]; then RET=0 - curl --silent --retry 10 -kLy 5 -o $FILE $URL || RET=$? + curl --silent --retry 10 -Ly 5 -o $FILE $URL || RET=$? else RET=0 - curl --retry 10 -kLy 5 -o $FILE $URL || RET=$? + curl --retry 10 -Ly 5 -o $FILE $URL || RET=$? fi if [ $RET -ne 0 ]; then From 2ed12a398dc7002d26d7177d4d5b2927dbac1a03 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 4 Aug 2020 01:46:54 +0000 Subject: [PATCH 27/28] addLineDirectivesAfterConditionalBlocks move check for npos to catch all npos --- components/shader/shadermanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 490c9d438..be662990b 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -34,6 +34,9 @@ namespace Shader foundPos = source.find_first_of("\n\r", foundPos); foundPos = source.find_first_not_of("\n\r", foundPos); + if (foundPos == std::string::npos) + break; + size_t lineDirectivePosition = source.rfind("#line", foundPos); int lineNumber; if (lineDirectivePosition != std::string::npos) From 4bf24a955ee822024c17f276a08c2da07f765469 Mon Sep 17 00:00:00 2001 From: Perry Hugh Date: Tue, 4 Aug 2020 06:04:59 +0000 Subject: [PATCH 28/28] Restore Gamepad Zooming --- apps/openmw/mwbase/world.hpp | 1 + apps/openmw/mwinput/actionmanager.cpp | 5 +++++ apps/openmw/mwinput/actionmanager.hpp | 2 +- apps/openmw/mwinput/controllermanager.cpp | 4 ++-- apps/openmw/mwworld/worldimp.cpp | 5 +++++ apps/openmw/mwworld/worldimp.hpp | 1 + 6 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 17e233053..9709df362 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -416,6 +416,7 @@ namespace MWBase virtual void togglePOV(bool force = false) = 0; virtual bool isFirstPerson() const = 0; + virtual bool isPreviewModeEnabled() const = 0; virtual void togglePreviewMode(bool enable) = 0; virtual bool toggleVanityMode(bool enable) = 0; virtual void allowVanityMode(bool allow) = 0; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index ec6c5cf7f..adb5a1c8a 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -167,6 +167,11 @@ namespace MWInput mAttemptJump = false; } + + bool ActionManager::isPreviewModeEnabled() + { + return MWBase::Environment::get().getWorld()->isPreviewModeEnabled(); + } void ActionManager::resetIdleTime() { diff --git a/apps/openmw/mwinput/actionmanager.hpp b/apps/openmw/mwinput/actionmanager.hpp index 7aa73f520..eceac2e94 100644 --- a/apps/openmw/mwinput/actionmanager.hpp +++ b/apps/openmw/mwinput/actionmanager.hpp @@ -54,7 +54,7 @@ namespace MWInput void setAttemptJump(bool enabled) { mAttemptJump = enabled; } - float getPreviewDelay() const { return mPreviewPOVDelay; }; + bool isPreviewModeEnabled(); private: void handleGuiArrowKey(int action); diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index b0e769cc6..c9941c836 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -89,7 +89,7 @@ namespace MWInput bool ControllerManager::update(float dt) { - mGamepadPreviewMode = mActionManager->getPreviewDelay() == 1.f; + mGamepadPreviewMode = mActionManager->isPreviewModeEnabled(); if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled)) { @@ -287,7 +287,7 @@ namespace MWInput } else { - if (mGamepadPreviewMode && arg.value) // Preview Mode Gamepad Zooming + if (mGamepadPreviewMode) // Preview Mode Gamepad Zooming { if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 84f50cdef..2eec9bf0e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2394,6 +2394,11 @@ namespace MWWorld { return mRendering->getCamera()->isFirstPerson(); } + + bool World::isPreviewModeEnabled() const + { + return mRendering->getCamera()->getMode() == MWRender::Camera::Mode::Preview; + } void World::togglePreviewMode(bool enable) { diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index c08206600..5eae9608e 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -524,6 +524,7 @@ namespace MWWorld void togglePOV(bool force = false) override; bool isFirstPerson() const override; + bool isPreviewModeEnabled() const override; void togglePreviewMode(bool enable) override;