From 02730af391922f37663f1c39e8a4df42cb8ddc42 Mon Sep 17 00:00:00 2001 From: Kagernac Date: Sun, 19 May 2024 14:43:41 -0700 Subject: [PATCH 01/45] Changing the skin of the widget seems to be the easiest way to have the choices in the list react to specific conditions and interactions. Seems that changing the skin modifies some size properties so they had to be reset. Needed to create a specific skin in order to prevent modifying unintended text. The exhausted color is not referenced by the settings.cfg, the user will need to modify the color in the skin file. Edit: Fixed the coding convention issues and converted the tabs into spaces in the .xml. Closed the previous merge request out of confusion, sorry new to Git. --- apps/openmw/mwgui/dialogue.cpp | 19 +++++++++++++++---- files/data/mygui/openmw_list.skin.xml | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 56f69eb906..ba40fa92e1 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -794,18 +794,29 @@ namespace MWGui if (!Settings::gui().mColorTopicEnable) return; - const MyGUI::Colour& specialColour = Settings::gui().mColorTopicSpecific; - const MyGUI::Colour& oldColour = Settings::gui().mColorTopicExhausted; + std::string specificSkin = "MW_ListLine_Specific"; + std::string exhaustedSkin = "MW_ListLine_Exhausted"; for (const std::string& keyword : mKeywords) { int flag = MWBase::Environment::get().getDialogueManager()->getTopicFlag(ESM::RefId::stringRefId(keyword)); MyGUI::Button* button = mTopicsList->getItemWidget(keyword); + const auto holdCaption = button->getCaption(); if (flag & MWBase::DialogueManager::TopicType::Specific) - button->getSubWidgetText()->setTextColour(specialColour); + { + button->changeWidgetSkin(specificSkin); + button->setCaption(holdCaption); + int height = button->getTextSize().height; + button->setSize(MyGUI::IntSize(button->getSize().width, height)); + } else if (flag & MWBase::DialogueManager::TopicType::Exhausted) - button->getSubWidgetText()->setTextColour(oldColour); + { + button->changeWidgetSkin(exhaustedSkin); + button->setCaption(holdCaption); + int height = button->getTextSize().height; + button->setSize(MyGUI::IntSize(button->getSize().width, height)); + } } } diff --git a/files/data/mygui/openmw_list.skin.xml b/files/data/mygui/openmw_list.skin.xml index 75f4b9e850..877c827833 100644 --- a/files/data/mygui/openmw_list.skin.xml +++ b/files/data/mygui/openmw_list.skin.xml @@ -124,6 +124,28 @@ + + + + + + + + + + + + + + + + + + + + + + From f9577d904cf0b2c351919b3e70a47020470cb056 Mon Sep 17 00:00:00 2001 From: Kagernac Date: Wed, 22 May 2024 20:25:58 -0700 Subject: [PATCH 02/45] This commit introduces six new color settings for dialogue topic keywords referencing settings.cfg and settings-default.cfg. These settings work similarly to the color references found in openmw.cfg and Morrowind.ini. Changes include: Removal of deprecated settings: color topic specific color topic exhausted Addition of six new color settings to settings.cfg and settings-default.cfg Updated description in settings-default.cfg to reflect the new color format --- apps/openmw/mwgui/textcolours.cpp | 8 ++++++++ apps/openmw/mwgui/textcolours.hpp | 8 ++++++++ components/fallback/validate.cpp | 4 +++- components/settings/categories/gui.hpp | 8 ++++++-- components/widgets/tags.cpp | 9 +++++++-- files/data/mygui/openmw_list.skin.xml | 16 ++++++++-------- files/settings-default.cfg | 13 +++++++++---- 7 files changed, 49 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwgui/textcolours.cpp b/apps/openmw/mwgui/textcolours.cpp index 77afa5e265..c3c293297c 100644 --- a/apps/openmw/mwgui/textcolours.cpp +++ b/apps/openmw/mwgui/textcolours.cpp @@ -32,5 +32,13 @@ namespace MWGui journalTopic = getTextColour("journal_topic"); journalTopicOver = getTextColour("journal_topic_over"); journalTopicPressed = getTextColour("journal_topic_pressed"); + + specific = getTextColour("specific"); + specificOver = getTextColour("specific_over"); + specificPressed = getTextColour("specific_pressed"); + + exhausted = getTextColour("exhausted"); + exhaustedOver = getTextColour("exhausted_over"); + exhaustedPressed = getTextColour("exhausted_pressed"); } } diff --git a/apps/openmw/mwgui/textcolours.hpp b/apps/openmw/mwgui/textcolours.hpp index 83bc1d3f5a..add251a1c9 100644 --- a/apps/openmw/mwgui/textcolours.hpp +++ b/apps/openmw/mwgui/textcolours.hpp @@ -27,6 +27,14 @@ namespace MWGui MyGUI::Colour journalTopicOver; MyGUI::Colour journalTopicPressed; + MyGUI::Colour specific; + MyGUI::Colour specificOver; + MyGUI::Colour specificPressed; + + MyGUI::Colour exhausted; + MyGUI::Colour exhaustedOver; + MyGUI::Colour exhaustedPressed; + public: void loadColours(); }; diff --git a/components/fallback/validate.cpp b/components/fallback/validate.cpp index 571f0f9b19..5728880354 100644 --- a/components/fallback/validate.cpp +++ b/components/fallback/validate.cpp @@ -109,7 +109,9 @@ static const std::set allowedKeysNonNumeric = { "Blood_Model_0 "FontColor_color_link", "FontColor_color_link_over", "FontColor_color_link_pressed", "FontColor_color_magic", "FontColor_color_magic_fill", "FontColor_color_misc", "FontColor_color_negative", "FontColor_color_normal", "FontColor_color_normal_over", "FontColor_color_normal_pressed", "FontColor_color_notify", - "FontColor_color_positive", "FontColor_color_weapon_fill", "Fonts_Font_0", "Fonts_Font_1", "Fonts_Font_2", + "FontColor_color_positive", "FontColor_color_weapon_fill", "FontColor_color_specific", "FontColor_color_specific_over", + "FontColor_color_specific_pressed", "FontColor_color_exhausted", "FontColor_color_exhausted_over", "FontColor_color_exhausted_pressed", + "Fonts_Font_0", "Fonts_Font_1", "Fonts_Font_2", "Level_Up_Default", "Moons_Script_Color", "Movies_Company_Logo", "Movies_Morrowind_Logo", "Movies_New_Game", "Question_10_AnswerOne", "Question_10_AnswerThree", "Question_10_AnswerTwo", "Question_10_Question", "Question_10_Sound", "Question_1_AnswerOne", "Question_1_AnswerThree", "Question_1_AnswerTwo", diff --git a/components/settings/categories/gui.hpp b/components/settings/categories/gui.hpp index 4a5e50fd8a..88f6924638 100644 --- a/components/settings/categories/gui.hpp +++ b/components/settings/categories/gui.hpp @@ -32,8 +32,12 @@ namespace Settings SettingValue mColorCrosshairOwned{ mIndex, "GUI", "color crosshair owned" }; SettingValue mKeyboardNavigation{ mIndex, "GUI", "keyboard navigation" }; SettingValue mColorTopicEnable{ mIndex, "GUI", "color topic enable" }; - SettingValue mColorTopicSpecific{ mIndex, "GUI", "color topic specific" }; - SettingValue mColorTopicExhausted{ mIndex, "GUI", "color topic exhausted" }; + SettingValue mColorTopicSpecific{ mIndex, "GUI", "FontColor_color_specific" }; + SettingValue mColorTopicSpecificOver{ mIndex, "GUI", "FontColor_color_specific_over" }; + SettingValue mColorTopicSpecificPressed{ mIndex, "GUI", "FontColor_color_specific_pressed" }; + SettingValue mColorTopicExhausted{ mIndex, "GUI", "FontColor_color_exhausted" }; + SettingValue mColorTopicExhaustedOver{ mIndex, "GUI", "FontColor_color_exhausted_over" }; + SettingValue mColorTopicExhaustedPressed{ mIndex, "GUI", "FontColor_color_exhausted_pressed" }; }; } diff --git a/components/widgets/tags.cpp b/components/widgets/tags.cpp index 3fe72480b7..e10cbeda88 100644 --- a/components/widgets/tags.cpp +++ b/components/widgets/tags.cpp @@ -1,5 +1,6 @@ #include "tags.hpp" +#include #include #include @@ -10,7 +11,6 @@ namespace Gui bool replaceTag(std::string_view tag, MyGUI::UString& out) { std::string_view fontcolour = "fontcolour="; - std::string_view fontcolourhtml = "fontcolourhtml="; if (tag.starts_with(fontcolour)) @@ -19,7 +19,12 @@ namespace Gui fallbackName += tag.substr(fontcolour.length()); std::string_view str = Fallback::Map::getString(fallbackName); if (str.empty()) - throw std::runtime_error("Unknown fallback name: " + fallbackName); + { + std::string_view category = "GUI"; + str = Settings::Manager::getString(fallbackName, category); + if (str.empty()) + throw std::runtime_error("Unable to map setting to value: " + fallbackName); + } std::string ret[3]; unsigned int j = 0; diff --git a/files/data/mygui/openmw_list.skin.xml b/files/data/mygui/openmw_list.skin.xml index 877c827833..10524c6d1f 100644 --- a/files/data/mygui/openmw_list.skin.xml +++ b/files/data/mygui/openmw_list.skin.xml @@ -127,22 +127,22 @@ - + - - - + + + - + - - - + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 9d15f44401..3f2963dc17 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -218,14 +218,19 @@ keyboard navigation = true color topic enable = false # The color of dialogue topic keywords that gives unique actor responses -# Format R G B A or empty for no special formatting +# Format (R,G,B) (255,255,255) or empty for no special formatting # Default to blue -color topic specific = 0.45 0.5 0.8 1 +FontColor_color_specific = 112,126,207 +FontColor_color_specific_over = 143,155,218 +FontColor_color_specific_pressed = 175,184,228 + # The color of dialogue topic keywords that gives already read responses -# Format R G B A or empty for no special formatting +# Format (R,G,B) (255,255,255) or empty for no special formatting # Default to grey -color topic exhausted = 0.3 0.3 0.3 1 +FontColor_color_exhausted = 103,103,111 +FontColor_color_exhausted_over = 170,170,188 +FontColor_color_exhausted_pressed = 69,69,85 [HUD] From 3e6ccfce1ff7b384d9f7b5b33fd42f139767fee7 Mon Sep 17 00:00:00 2001 From: Kagernac Date: Tue, 4 Jun 2024 23:14:44 -0700 Subject: [PATCH 03/45] Took @Capostrophic's suggestion about leveraging a new tag format Removed the color settings from Textcolours, they are unneccessary Removed the Fallback workaround as a dedicated else condition was created with the new tag "fontcolouroptional". This code section has no involvement in Fallback openmw_list.skin.xml was updated to reflect this new tag settings.cfg was updated with the updated variable names --- apps/openmw/mwgui/textcolours.cpp | 8 +------ apps/openmw/mwgui/textcolours.hpp | 8 ------- components/fallback/validate.cpp | 3 +-- components/settings/categories/gui.hpp | 12 +++++----- components/widgets/tags.cpp | 32 +++++++++++++++++++++----- files/data/mygui/openmw_list.skin.xml | 12 +++++----- files/settings-default.cfg | 12 +++++----- 7 files changed, 46 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwgui/textcolours.cpp b/apps/openmw/mwgui/textcolours.cpp index c3c293297c..981817a4a9 100644 --- a/apps/openmw/mwgui/textcolours.cpp +++ b/apps/openmw/mwgui/textcolours.cpp @@ -11,6 +11,7 @@ namespace MWGui return MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=" + type + "}")); } + void TextColours::loadColours() { header = getTextColour("header"); @@ -33,12 +34,5 @@ namespace MWGui journalTopicOver = getTextColour("journal_topic_over"); journalTopicPressed = getTextColour("journal_topic_pressed"); - specific = getTextColour("specific"); - specificOver = getTextColour("specific_over"); - specificPressed = getTextColour("specific_pressed"); - - exhausted = getTextColour("exhausted"); - exhaustedOver = getTextColour("exhausted_over"); - exhaustedPressed = getTextColour("exhausted_pressed"); } } diff --git a/apps/openmw/mwgui/textcolours.hpp b/apps/openmw/mwgui/textcolours.hpp index add251a1c9..83bc1d3f5a 100644 --- a/apps/openmw/mwgui/textcolours.hpp +++ b/apps/openmw/mwgui/textcolours.hpp @@ -27,14 +27,6 @@ namespace MWGui MyGUI::Colour journalTopicOver; MyGUI::Colour journalTopicPressed; - MyGUI::Colour specific; - MyGUI::Colour specificOver; - MyGUI::Colour specificPressed; - - MyGUI::Colour exhausted; - MyGUI::Colour exhaustedOver; - MyGUI::Colour exhaustedPressed; - public: void loadColours(); }; diff --git a/components/fallback/validate.cpp b/components/fallback/validate.cpp index 5728880354..f6868e56f4 100644 --- a/components/fallback/validate.cpp +++ b/components/fallback/validate.cpp @@ -109,8 +109,7 @@ static const std::set allowedKeysNonNumeric = { "Blood_Model_0 "FontColor_color_link", "FontColor_color_link_over", "FontColor_color_link_pressed", "FontColor_color_magic", "FontColor_color_magic_fill", "FontColor_color_misc", "FontColor_color_negative", "FontColor_color_normal", "FontColor_color_normal_over", "FontColor_color_normal_pressed", "FontColor_color_notify", - "FontColor_color_positive", "FontColor_color_weapon_fill", "FontColor_color_specific", "FontColor_color_specific_over", - "FontColor_color_specific_pressed", "FontColor_color_exhausted", "FontColor_color_exhausted_over", "FontColor_color_exhausted_pressed", + "FontColor_color_positive", "FontColor_color_weapon_fill", "Fonts_Font_0", "Fonts_Font_1", "Fonts_Font_2", "Level_Up_Default", "Moons_Script_Color", "Movies_Company_Logo", "Movies_Morrowind_Logo", "Movies_New_Game", "Question_10_AnswerOne", "Question_10_AnswerThree", "Question_10_AnswerTwo", "Question_10_Question", diff --git a/components/settings/categories/gui.hpp b/components/settings/categories/gui.hpp index 88f6924638..a26364c5dd 100644 --- a/components/settings/categories/gui.hpp +++ b/components/settings/categories/gui.hpp @@ -32,12 +32,12 @@ namespace Settings SettingValue mColorCrosshairOwned{ mIndex, "GUI", "color crosshair owned" }; SettingValue mKeyboardNavigation{ mIndex, "GUI", "keyboard navigation" }; SettingValue mColorTopicEnable{ mIndex, "GUI", "color topic enable" }; - SettingValue mColorTopicSpecific{ mIndex, "GUI", "FontColor_color_specific" }; - SettingValue mColorTopicSpecificOver{ mIndex, "GUI", "FontColor_color_specific_over" }; - SettingValue mColorTopicSpecificPressed{ mIndex, "GUI", "FontColor_color_specific_pressed" }; - SettingValue mColorTopicExhausted{ mIndex, "GUI", "FontColor_color_exhausted" }; - SettingValue mColorTopicExhaustedOver{ mIndex, "GUI", "FontColor_color_exhausted_over" }; - SettingValue mColorTopicExhaustedPressed{ mIndex, "GUI", "FontColor_color_exhausted_pressed" }; + SettingValue mColorTopicSpecific{ mIndex, "GUI", "color topic specific" }; + SettingValue mColorTopicSpecificOver{ mIndex, "GUI", "color topic specific over" }; + SettingValue mColorTopicSpecificPressed{ mIndex, "GUI", "color topic specific pressed" }; + SettingValue mColorTopicExhausted{ mIndex, "GUI", "color topic exhausted" }; + SettingValue mColorTopicExhaustedOver{ mIndex, "GUI", "color topic exhausted over" }; + SettingValue mColorTopicExhaustedPressed{ mIndex, "GUI", "color topic exhausted pressed" }; }; } diff --git a/components/widgets/tags.cpp b/components/widgets/tags.cpp index e10cbeda88..ae55e4b76d 100644 --- a/components/widgets/tags.cpp +++ b/components/widgets/tags.cpp @@ -12,6 +12,7 @@ namespace Gui { std::string_view fontcolour = "fontcolour="; std::string_view fontcolourhtml = "fontcolourhtml="; + std::string_view fontcolouroptional = "fontcolouroptional="; if (tag.starts_with(fontcolour)) { @@ -19,12 +20,7 @@ namespace Gui fallbackName += tag.substr(fontcolour.length()); std::string_view str = Fallback::Map::getString(fallbackName); if (str.empty()) - { - std::string_view category = "GUI"; - str = Settings::Manager::getString(fallbackName, category); - if (str.empty()) - throw std::runtime_error("Unable to map setting to value: " + fallbackName); - } + throw std::runtime_error("Unable to map setting to value: " + fallbackName); std::string ret[3]; unsigned int j = 0; @@ -63,6 +59,30 @@ namespace Gui out = html.str(); return true; } + else if (tag.starts_with(fontcolouroptional)) + { + std::string settingprefix = "color topic "; + std::string_view category = "GUI"; + settingprefix += tag.substr(fontcolouroptional.length()); + std::replace(settingprefix.begin(), settingprefix.end(), '_', ' '); + std::string str = Settings::Manager::getString(settingprefix, category); + if (str.empty()) + throw std::runtime_error("Unable to map setting to value: " + settingprefix); + + std::string ret[3]; + unsigned int j = 0; + for (unsigned int i = 0; i < str.length(); ++i) + { + if (str[i] == ',') + j++; + else if (str[i] != ' ') + ret[j] += str[i]; + } + MyGUI::Colour col(MyGUI::utility::parseInt(ret[0]) / 255.f, MyGUI::utility::parseInt(ret[1]) / 255.f, + MyGUI::utility::parseInt(ret[2]) / 255.f); + out = col.print(); + return true; + } return false; } diff --git a/files/data/mygui/openmw_list.skin.xml b/files/data/mygui/openmw_list.skin.xml index 10524c6d1f..0e57f44992 100644 --- a/files/data/mygui/openmw_list.skin.xml +++ b/files/data/mygui/openmw_list.skin.xml @@ -129,9 +129,9 @@ - - - + + + @@ -140,9 +140,9 @@ - - - + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 3f2963dc17..9a262260e7 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -220,17 +220,17 @@ color topic enable = false # The color of dialogue topic keywords that gives unique actor responses # Format (R,G,B) (255,255,255) or empty for no special formatting # Default to blue -FontColor_color_specific = 112,126,207 -FontColor_color_specific_over = 143,155,218 -FontColor_color_specific_pressed = 175,184,228 +color topic specific = 112,126,207 +color topic specific over = 143,155,218 +color topic specific pressed = 175,184,228 # The color of dialogue topic keywords that gives already read responses # Format (R,G,B) (255,255,255) or empty for no special formatting # Default to grey -FontColor_color_exhausted = 103,103,111 -FontColor_color_exhausted_over = 170,170,188 -FontColor_color_exhausted_pressed = 69,69,85 +color topic exhausted = 103,103,111 +color topic exhausted over = 170,170,188 +color topic exhausted pressed= 69,69,85 [HUD] From aca39c919fce6d788407603344bdc8c099b1e7fe Mon Sep 17 00:00:00 2001 From: Kagernac Date: Wed, 5 Jun 2024 20:26:45 -0700 Subject: [PATCH 04/45] Changed the tagname to match the values in settings.cfg Changed the values from int to float in settings.cfg. This is consistent with the older format. The prefix is no longer referenced and hardcoded. There were four original values in the old color format, the parsing had to be modified to account for floats and four values. --- components/widgets/tags.cpp | 17 ++++++++--------- files/data/mygui/openmw_list.skin.xml | 12 ++++++------ files/settings-default.cfg | 12 ++++++------ 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/components/widgets/tags.cpp b/components/widgets/tags.cpp index ae55e4b76d..1477470024 100644 --- a/components/widgets/tags.cpp +++ b/components/widgets/tags.cpp @@ -61,25 +61,24 @@ namespace Gui } else if (tag.starts_with(fontcolouroptional)) { - std::string settingprefix = "color topic "; std::string_view category = "GUI"; - settingprefix += tag.substr(fontcolouroptional.length()); - std::replace(settingprefix.begin(), settingprefix.end(), '_', ' '); - std::string str = Settings::Manager::getString(settingprefix, category); + std::string colortag = ""; + colortag += tag.substr(fontcolouroptional.length()); + std::string str = Settings::Manager::getString(colortag, category); if (str.empty()) - throw std::runtime_error("Unable to map setting to value: " + settingprefix); + throw std::runtime_error("Unable to map setting to value: " + colortag); - std::string ret[3]; + std::string ret[4]; unsigned int j = 0; for (unsigned int i = 0; i < str.length(); ++i) { - if (str[i] == ',') + if (str[i] == ' ') j++; else if (str[i] != ' ') ret[j] += str[i]; } - MyGUI::Colour col(MyGUI::utility::parseInt(ret[0]) / 255.f, MyGUI::utility::parseInt(ret[1]) / 255.f, - MyGUI::utility::parseInt(ret[2]) / 255.f); + MyGUI::Colour col(MyGUI::utility::parseFloat(ret[0]), MyGUI::utility::parseFloat(ret[1]), + MyGUI::utility::parseFloat(ret[2]), MyGUI::utility::parseFloat(ret[3])); out = col.print(); return true; } diff --git a/files/data/mygui/openmw_list.skin.xml b/files/data/mygui/openmw_list.skin.xml index 0e57f44992..45551429ed 100644 --- a/files/data/mygui/openmw_list.skin.xml +++ b/files/data/mygui/openmw_list.skin.xml @@ -129,9 +129,9 @@ - - - + + + @@ -140,9 +140,9 @@ - - - + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 9a262260e7..1efcec8db4 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -220,17 +220,17 @@ color topic enable = false # The color of dialogue topic keywords that gives unique actor responses # Format (R,G,B) (255,255,255) or empty for no special formatting # Default to blue -color topic specific = 112,126,207 -color topic specific over = 143,155,218 -color topic specific pressed = 175,184,228 +color topic specific = 0.45 0.5 0.8 1 +color topic specific over = 0.58 0.62 0.85 1 +color topic specific pressed = 0.29 0.36 0.75 1 # The color of dialogue topic keywords that gives already read responses # Format (R,G,B) (255,255,255) or empty for no special formatting # Default to grey -color topic exhausted = 103,103,111 -color topic exhausted over = 170,170,188 -color topic exhausted pressed= 69,69,85 +color topic exhausted = 0.3 0.3 0.3 1 +color topic exhausted over = 0.56 0.56 0.56 1 +color topic exhausted pressed= 0.44 0.44 0.44 1 [HUD] From 7d403089ec01ffc6d1429f6ebf401949152ada3c Mon Sep 17 00:00:00 2001 From: Kagernac Date: Fri, 7 Jun 2024 18:38:32 -0700 Subject: [PATCH 05/45] Cleaner implementation Retrieved the Colours straight from Settings Made tagname clearer in openmw_list.skin.xml Fixed minor formatting issue in validate.cpp, textcolours.cpp Updated skin variables to be const and string_view --- apps/openmw/mwgui/dialogue.cpp | 4 ++-- apps/openmw/mwgui/textcolours.cpp | 2 -- components/fallback/validate.cpp | 3 +-- components/widgets/tags.cpp | 24 ++++-------------------- files/data/mygui/openmw_list.skin.xml | 12 ++++++------ 5 files changed, 13 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index ba40fa92e1..56d2699175 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -794,8 +794,8 @@ namespace MWGui if (!Settings::gui().mColorTopicEnable) return; - std::string specificSkin = "MW_ListLine_Specific"; - std::string exhaustedSkin = "MW_ListLine_Exhausted"; + const std::string_view specificSkin = "MW_ListLine_Specific"; + const std::string_view exhaustedSkin = "MW_ListLine_Exhausted"; for (const std::string& keyword : mKeywords) { diff --git a/apps/openmw/mwgui/textcolours.cpp b/apps/openmw/mwgui/textcolours.cpp index 981817a4a9..77afa5e265 100644 --- a/apps/openmw/mwgui/textcolours.cpp +++ b/apps/openmw/mwgui/textcolours.cpp @@ -11,7 +11,6 @@ namespace MWGui return MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=" + type + "}")); } - void TextColours::loadColours() { header = getTextColour("header"); @@ -33,6 +32,5 @@ namespace MWGui journalTopic = getTextColour("journal_topic"); journalTopicOver = getTextColour("journal_topic_over"); journalTopicPressed = getTextColour("journal_topic_pressed"); - } } diff --git a/components/fallback/validate.cpp b/components/fallback/validate.cpp index f6868e56f4..571f0f9b19 100644 --- a/components/fallback/validate.cpp +++ b/components/fallback/validate.cpp @@ -109,8 +109,7 @@ static const std::set allowedKeysNonNumeric = { "Blood_Model_0 "FontColor_color_link", "FontColor_color_link_over", "FontColor_color_link_pressed", "FontColor_color_magic", "FontColor_color_magic_fill", "FontColor_color_misc", "FontColor_color_negative", "FontColor_color_normal", "FontColor_color_normal_over", "FontColor_color_normal_pressed", "FontColor_color_notify", - "FontColor_color_positive", "FontColor_color_weapon_fill", - "Fonts_Font_0", "Fonts_Font_1", "Fonts_Font_2", + "FontColor_color_positive", "FontColor_color_weapon_fill", "Fonts_Font_0", "Fonts_Font_1", "Fonts_Font_2", "Level_Up_Default", "Moons_Script_Color", "Movies_Company_Logo", "Movies_Morrowind_Logo", "Movies_New_Game", "Question_10_AnswerOne", "Question_10_AnswerThree", "Question_10_AnswerTwo", "Question_10_Question", "Question_10_Sound", "Question_1_AnswerOne", "Question_1_AnswerThree", "Question_1_AnswerTwo", diff --git a/components/widgets/tags.cpp b/components/widgets/tags.cpp index 1477470024..d5796c35b5 100644 --- a/components/widgets/tags.cpp +++ b/components/widgets/tags.cpp @@ -12,7 +12,7 @@ namespace Gui { std::string_view fontcolour = "fontcolour="; std::string_view fontcolourhtml = "fontcolourhtml="; - std::string_view fontcolouroptional = "fontcolouroptional="; + std::string_view fontcolouroptional = "fontcoloursetting="; if (tag.starts_with(fontcolour)) { @@ -61,25 +61,9 @@ namespace Gui } else if (tag.starts_with(fontcolouroptional)) { - std::string_view category = "GUI"; - std::string colortag = ""; - colortag += tag.substr(fontcolouroptional.length()); - std::string str = Settings::Manager::getString(colortag, category); - if (str.empty()) - throw std::runtime_error("Unable to map setting to value: " + colortag); - - std::string ret[4]; - unsigned int j = 0; - for (unsigned int i = 0; i < str.length(); ++i) - { - if (str[i] == ' ') - j++; - else if (str[i] != ' ') - ret[j] += str[i]; - } - MyGUI::Colour col(MyGUI::utility::parseFloat(ret[0]), MyGUI::utility::parseFloat(ret[1]), - MyGUI::utility::parseFloat(ret[2]), MyGUI::utility::parseFloat(ret[3])); - out = col.print(); + std::string_view colortag = tag.substr(fontcolouroptional.length()); + const MyGUI::Colour& customColour = Settings::get("GUI", colortag).get(); + out = customColour.print(); return true; } return false; diff --git a/files/data/mygui/openmw_list.skin.xml b/files/data/mygui/openmw_list.skin.xml index 45551429ed..30ebdca35d 100644 --- a/files/data/mygui/openmw_list.skin.xml +++ b/files/data/mygui/openmw_list.skin.xml @@ -129,9 +129,9 @@ - - - + + + @@ -140,9 +140,9 @@ - - - + + + From f7628ff0a82f1fb73f2fc9fc70c2dd50281ef4ac Mon Sep 17 00:00:00 2001 From: Kagernac Date: Wed, 19 Jun 2024 11:08:05 -0700 Subject: [PATCH 06/45] Code cleanup and documentation Added documentation in GUI.rst for the new settings Cleaned up the MR with some remnant changes. --- components/widgets/tags.cpp | 2 +- .../source/reference/modding/settings/GUI.rst | 52 +++++++++++++++++++ files/settings-default.cfg | 4 +- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/components/widgets/tags.cpp b/components/widgets/tags.cpp index d5796c35b5..ddca0171c8 100644 --- a/components/widgets/tags.cpp +++ b/components/widgets/tags.cpp @@ -20,7 +20,7 @@ namespace Gui fallbackName += tag.substr(fontcolour.length()); std::string_view str = Fallback::Map::getString(fallbackName); if (str.empty()) - throw std::runtime_error("Unable to map setting to value: " + fallbackName); + throw std::runtime_error("Unknown fallback name: " + fallbackName); std::string ret[3]; unsigned int j = 0; diff --git a/docs/source/reference/modding/settings/GUI.rst b/docs/source/reference/modding/settings/GUI.rst index edacdc730a..0369e23651 100644 --- a/docs/source/reference/modding/settings/GUI.rst +++ b/docs/source/reference/modding/settings/GUI.rst @@ -157,6 +157,32 @@ The alpha value is currently ignored. 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 specific over +-------------------- + +:Type: RGBA floating point +:Range: 0.0 to 1.0 +:Default: empty + +This setting provides an "over" colour to dialogue topics that meet the color topic specific criteria. +The value is composed of four floating point values representing the red, green, blue and alpha channels. +The alpha value is currently ignored. + +A dialogue topic is considered "over" if it is the active GUI element through keyboard or mouse events. + +color topic specific pressed +-------------------- + +:Type: RGBA floating point +:Range: 0.0 to 1.0 +:Default: empty + +This setting provides an "pressed" colour to dialogue topics that meet the color topic specific criteria. +The value is composed of four floating point values representing the red, green, blue and alpha channels. +The alpha value is currently ignored. + +A dialogue topic is considered "pressed" if it is the active GUI element and it receives a sustained keyboard or mouse event. + color topic exhausted --------------------- @@ -169,3 +195,29 @@ The value is composed of four floating point values representing the red, green, The alpha value is currently ignored. A topic is considered "exhausted" if the response the player is about to see has already been seen. + +color topic exhausted over +-------------------- + +:Type: RGBA floating point +:Range: 0.0 to 1.0 +:Default: empty + +This setting provides an "over" colour to dialogue topics that meet the color topic exhausted criteria. +The value is composed of four floating point values representing the red, green, blue and alpha channels. +The alpha value is currently ignored. + +A dialogue topic is considered "over" if it is the active GUI element through keyboard or mouse events. + +color topic exhausted pressed +-------------------- + +:Type: RGBA floating point +:Range: 0.0 to 1.0 +:Default: empty + +This setting provides an "pressed" colour to dialogue topics that meet the color topic exhausted criteria. +The value is composed of four floating point values representing the red, green, blue and alpha channels. +The alpha value is currently ignored. + +A dialogue topic is considered "pressed" if it is the active GUI element and it receives a sustained keyboard or mouse event. \ No newline at end of file diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 1efcec8db4..d5c6e01398 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -218,7 +218,7 @@ keyboard navigation = true color topic enable = false # The color of dialogue topic keywords that gives unique actor responses -# Format (R,G,B) (255,255,255) or empty for no special formatting +# Format R G B A or empty for no special formatting # Default to blue color topic specific = 0.45 0.5 0.8 1 color topic specific over = 0.58 0.62 0.85 1 @@ -226,7 +226,7 @@ color topic specific pressed = 0.29 0.36 0.75 1 # The color of dialogue topic keywords that gives already read responses -# Format (R,G,B) (255,255,255) or empty for no special formatting +# Format R G B A or empty for no special formatting # Default to grey color topic exhausted = 0.3 0.3 0.3 1 color topic exhausted over = 0.56 0.56 0.56 1 From ca8869042bd307632803381f687223e34bce55c1 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 15 Jun 2024 00:27:25 +0200 Subject: [PATCH 07/45] Move LiveCellRef where possible --- apps/openmw/mwworld/cellstore.cpp | 8 ++++---- apps/openmw/mwworld/containerstore.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 6f3d23593b..c22f873fe9 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -319,7 +319,7 @@ namespace // new reference MWWorld::LiveCellRef ref(record); ref.load(state); - collection.mList.push_back(ref); + collection.mList.push_back(std::move(ref)); MWWorld::LiveCellRefBase* base = &collection.mList.back(); MWBase::Environment::get().getWorldModel()->registerPtr(MWWorld::Ptr(base, cellstore)); @@ -426,9 +426,9 @@ namespace MWWorld liveCellRef.mData.setDeletedByContentFile(true); if (iter != mList.end()) - *iter = liveCellRef; + *iter = std::move(liveCellRef); else - mList.push_back(liveCellRef); + mList.push_back(std::move(liveCellRef)); } else { @@ -455,7 +455,7 @@ namespace MWWorld LiveCellRef liveCellRef(ref, ptr); if (!isEnabled(ref, esmStore)) liveCellRef.mData.disable(); - list.push_back(liveCellRef); + list.push_back(std::move(liveCellRef)); } template diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index f48f73f48a..5e020ac886 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -103,7 +103,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState( LiveCellRef ref(record); ref.load(state); - collection.mList.push_back(ref); + collection.mList.push_back(std::move(ref)); auto it = ContainerStoreIterator(this, --collection.mList.end()); MWBase::Environment::get().getWorldModel()->registerPtr(*it); From 4565152b3d8538965821d2343a5419a8c745deb4 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 15 Jun 2024 00:31:43 +0200 Subject: [PATCH 08/45] Define LiveCellRefBase functions inside MWWorld namespace block --- apps/openmw/mwworld/livecellref.cpp | 148 ++++++++++++++-------------- apps/openmw/mwworld/livecellref.hpp | 2 +- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index 61b838bbf0..5623083de7 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -15,104 +15,104 @@ #include "ptr.hpp" #include "worldmodel.hpp" -MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM::CellRef& cref) - : mClass(&Class::get(type)) - , mRef(cref) - , mData(cref) +namespace MWWorld { -} + LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM::CellRef& cref) + : mClass(&Class::get(type)) + , mRef(cref) + , mData(cref) + { + } -MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM4::Reference& cref) - : mClass(&Class::get(type)) - , mRef(cref) - , mData(cref) -{ -} + LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM4::Reference& cref) + : mClass(&Class::get(type)) + , mRef(cref) + , mData(cref) + { + } -MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM4::ActorCharacter& cref) - : mClass(&Class::get(type)) - , mRef(cref) - , mData(cref) -{ -} + LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM4::ActorCharacter& cref) + : mClass(&Class::get(type)) + , mRef(cref) + , mData(cref) + { + } -MWWorld::LiveCellRefBase::~LiveCellRefBase() -{ - MWBase::Environment::get().getWorldModel()->deregisterLiveCellRef(*this); -} + LiveCellRefBase::~LiveCellRefBase() + { + MWBase::Environment::get().getWorldModel()->deregisterLiveCellRef(*this); + } -void MWWorld::LiveCellRefBase::loadImp(const ESM::ObjectState& state) -{ - mRef = MWWorld::CellRef(state.mRef); - mData = RefData(state, mData.isDeletedByContentFile()); + void LiveCellRefBase::loadImp(const ESM::ObjectState& state) + { + mRef = CellRef(state.mRef); + mData = RefData(state, mData.isDeletedByContentFile()); - Ptr ptr(this); + Ptr ptr(this); - if (state.mHasLocals) - { - const ESM::RefId& scriptId = mClass->getScript(ptr); - // Make sure we still have a script. It could have been coming from a content file that is no longer active. - if (!scriptId.empty()) + if (state.mHasLocals) { - if (const ESM::Script* script - = MWBase::Environment::get().getESMStore()->get().search(scriptId)) + const ESM::RefId& scriptId = mClass->getScript(ptr); + // Make sure we still have a script. It could have been coming from a content file that is no longer active. + if (!scriptId.empty()) { - try - { - mData.setLocals(*script); - mData.getLocals().read(state.mLocals, scriptId); - } - catch (const std::exception& exception) + if (const ESM::Script* script + = MWBase::Environment::get().getESMStore()->get().search(scriptId)) { - Log(Debug::Error) << "Error: failed to load state for local script " << scriptId - << " because an exception has been thrown: " << exception.what(); + try + { + mData.setLocals(*script); + mData.getLocals().read(state.mLocals, scriptId); + } + catch (const std::exception& exception) + { + Log(Debug::Error) << "Error: failed to load state for local script " << scriptId + << " because an exception has been thrown: " << exception.what(); + } } } } - } - mClass->readAdditionalState(ptr, state); + mClass->readAdditionalState(ptr, state); - if (!mRef.getSoul().empty() - && !MWBase::Environment::get().getESMStore()->get().search(mRef.getSoul())) - { - Log(Debug::Warning) << "Soul '" << mRef.getSoul() << "' not found, removing the soul from soul gem"; - mRef.setSoul(ESM::RefId()); - } + if (!mRef.getSoul().empty() + && !MWBase::Environment::get().getESMStore()->get().search(mRef.getSoul())) + { + Log(Debug::Warning) << "Soul '" << mRef.getSoul() << "' not found, removing the soul from soul gem"; + mRef.setSoul(ESM::RefId()); + } - MWBase::Environment::get().getLuaManager()->loadLocalScripts(ptr, state.mLuaScripts); -} + MWBase::Environment::get().getLuaManager()->loadLocalScripts(ptr, state.mLuaScripts); + } -void MWWorld::LiveCellRefBase::saveImp(ESM::ObjectState& state) const -{ - mRef.writeState(state); + void LiveCellRefBase::saveImp(ESM::ObjectState& state) const + { + mRef.writeState(state); - ConstPtr ptr(this); + ConstPtr ptr(this); - mData.write(state, mClass->getScript(ptr)); - MWBase::Environment::get().getLuaManager()->saveLocalScripts( - Ptr(const_cast(this)), state.mLuaScripts); + mData.write(state, mClass->getScript(ptr)); + MWBase::Environment::get().getLuaManager()->saveLocalScripts( + Ptr(const_cast(this)), state.mLuaScripts); - mClass->writeAdditionalState(ptr, state); -} + mClass->writeAdditionalState(ptr, state); + } -bool MWWorld::LiveCellRefBase::checkStateImp(const ESM::ObjectState& state) -{ - return true; -} + bool LiveCellRefBase::checkStateImp(const ESM::ObjectState& state) + { + return true; + } -unsigned int MWWorld::LiveCellRefBase::getType() const -{ - return mClass->getType(); -} + unsigned int LiveCellRefBase::getType() const + { + return mClass->getType(); + } -bool MWWorld::LiveCellRefBase::isDeleted() const -{ - return mData.isDeletedByContentFile() || mRef.getCount(false) == 0; -} + bool LiveCellRefBase::isDeleted() const + { + return mData.isDeletedByContentFile() || mRef.getCount(false) == 0; + } -namespace MWWorld -{ std::string makeDynamicCastErrorMessage(const LiveCellRefBase* value, std::string_view recordType) { std::stringstream message; diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index c95dd589b2..5694e33642 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -29,7 +29,7 @@ namespace MWWorld /** Information about this instance, such as 3D location and rotation * and individual type-dependent data. */ - MWWorld::CellRef mRef; + CellRef mRef; /** runtime-data */ RefData mData; From d998faec1b5c14dbd3f2652a551c631d635f045c Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 15 Jun 2024 00:30:14 +0200 Subject: [PATCH 09/45] Deregister only registered LiveCellRefBase --- apps/openmw/mwworld/livecellref.cpp | 20 +++++++++++++++++++- apps/openmw/mwworld/livecellref.hpp | 12 ++++++++++++ apps/openmw/mwworld/worldmodel.cpp | 15 +++++++++++++++ apps/openmw/mwworld/worldmodel.hpp | 4 ++-- 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index 5623083de7..aaa74f5ef3 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -38,9 +38,27 @@ namespace MWWorld { } + LiveCellRefBase::LiveCellRefBase(LiveCellRefBase&& other) noexcept + : mClass(other.mClass) + , mRef(std::move(other.mRef)) + , mData(std::move(other.mData)) + , mWorldModel(std::exchange(other.mWorldModel, nullptr)) + { + } + LiveCellRefBase::~LiveCellRefBase() { - MWBase::Environment::get().getWorldModel()->deregisterLiveCellRef(*this); + if (mWorldModel != nullptr) + mWorldModel->deregisterLiveCellRef(*this); + } + + LiveCellRefBase& LiveCellRefBase::operator=(LiveCellRefBase&& other) noexcept + { + mClass = other.mClass; + mRef = std::move(other.mRef); + mData = std::move(other.mData); + mWorldModel = std::exchange(other.mWorldModel, nullptr); + return *this; } void LiveCellRefBase::loadImp(const ESM::ObjectState& state) diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index 5694e33642..f3b48c0ff1 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -17,6 +17,7 @@ namespace MWWorld class Ptr; class ESMStore; class Class; + class WorldModel; template struct LiveCellRef; @@ -34,12 +35,23 @@ namespace MWWorld /** runtime-data */ RefData mData; + WorldModel* mWorldModel = nullptr; + LiveCellRefBase(unsigned int type, const ESM::CellRef& cref = ESM::CellRef()); LiveCellRefBase(unsigned int type, const ESM4::Reference& cref); LiveCellRefBase(unsigned int type, const ESM4::ActorCharacter& cref); + + LiveCellRefBase(const LiveCellRefBase& other) = default; + + LiveCellRefBase(LiveCellRefBase&& other) noexcept; + /* Need this for the class to be recognized as polymorphic */ virtual ~LiveCellRefBase(); + LiveCellRefBase& operator=(const LiveCellRefBase& other) = default; + + LiveCellRefBase& operator=(LiveCellRefBase&& other) noexcept; + virtual void load(const ESM::ObjectState& state) = 0; ///< Load state into a LiveCellRef, that has already been initialised with base and class. /// diff --git a/apps/openmw/mwworld/worldmodel.cpp b/apps/openmw/mwworld/worldmodel.cpp index 3a1c486f0e..39ed27e96b 100644 --- a/apps/openmw/mwworld/worldmodel.cpp +++ b/apps/openmw/mwworld/worldmodel.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -339,6 +340,20 @@ namespace MWWorld throw std::runtime_error(std::string("Can't find cell with name ") + std::string(name)); return *result; } + + void WorldModel::registerPtr(const Ptr& ptr) + { + if (ptr.mRef == nullptr) + throw std::logic_error("Ptr with nullptr mRef is not allowed to be registered"); + mPtrRegistry.insert(ptr); + ptr.mRef->mWorldModel = this; + } + + void WorldModel::deregisterLiveCellRef(LiveCellRefBase& ref) noexcept + { + mPtrRegistry.remove(ref); + ref.mWorldModel = nullptr; + } } MWWorld::Ptr MWWorld::WorldModel::getPtrByRefId(const ESM::RefId& name) diff --git a/apps/openmw/mwworld/worldmodel.hpp b/apps/openmw/mwworld/worldmodel.hpp index 4c39d866de..e4b161d16e 100644 --- a/apps/openmw/mwworld/worldmodel.hpp +++ b/apps/openmw/mwworld/worldmodel.hpp @@ -77,9 +77,9 @@ namespace MWWorld std::size_t getPtrRegistryRevision() const { return mPtrRegistry.getRevision(); } - void registerPtr(const Ptr& ptr) { mPtrRegistry.insert(ptr); } + void registerPtr(const Ptr& ptr); - void deregisterLiveCellRef(const LiveCellRefBase& ref) noexcept { mPtrRegistry.remove(ref); } + void deregisterLiveCellRef(LiveCellRefBase& ref) noexcept; void assignSaveFileRefNum(ESM::CellRef& ref) { mPtrRegistry.assign(ref); } From 1cdbbef7ee90834816fe106192d3afdf1c8e52e1 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 15 Jun 2024 00:40:05 +0200 Subject: [PATCH 10/45] Use blank CellRef as default Default constructed CellRef has some fields uninitialized. --- apps/openmw/mwrender/characterpreview.cpp | 2 +- apps/openmw/mwworld/cellstore.cpp | 2 +- apps/openmw/mwworld/containerstore.cpp | 2 +- apps/openmw/mwworld/livecellref.hpp | 8 +------- apps/openmw/mwworld/player.cpp | 19 +++++++++++++------ components/esm3/cellref.cpp | 6 ++++++ components/esm3/cellref.hpp | 2 ++ 7 files changed, 25 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index a4c0181d35..123eadfdec 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -533,7 +533,7 @@ namespace MWRender : CharacterPreview( parent, resourceSystem, MWMechanics::getPlayer(), 512, 512, osg::Vec3f(0, 125, 8), osg::Vec3f(0, 0, 8)) , mBase(*mCharacter.get()->mBase) - , mRef(&mBase) + , mRef(ESM::makeBlankCellRef(), &mBase) , mPitchRadians(osg::DegreesToRadians(6.f)) { mCharacter = MWWorld::Ptr(&mRef, nullptr); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index c22f873fe9..4cd189bb20 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -317,7 +317,7 @@ namespace } // new reference - MWWorld::LiveCellRef ref(record); + MWWorld::LiveCellRef ref(ESM::makeBlankCellRef(), record); ref.load(state); collection.mList.push_back(std::move(ref)); diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 5e020ac886..5a7c5b5333 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -101,7 +101,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState( if (!record) return ContainerStoreIterator(this); - LiveCellRef ref(record); + LiveCellRef ref(ESM::makeBlankCellRef(), record); ref.load(state); collection.mList.push_back(std::move(ref)); auto it = ContainerStoreIterator(this, --collection.mList.end()); diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index f3b48c0ff1..026d3edefd 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -37,7 +37,7 @@ namespace MWWorld WorldModel* mWorldModel = nullptr; - LiveCellRefBase(unsigned int type, const ESM::CellRef& cref = ESM::CellRef()); + LiveCellRefBase(unsigned int type, const ESM::CellRef& cref); LiveCellRefBase(unsigned int type, const ESM4::Reference& cref); LiveCellRefBase(unsigned int type, const ESM4::ActorCharacter& cref); @@ -144,12 +144,6 @@ namespace MWWorld { } - LiveCellRef(const X* b = nullptr) - : LiveCellRefBase(X::sRecordId) - , mBase(b) - { - } - // The object that this instance is based on. const X* mBase; diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index f5d38e8686..b776f27f06 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -36,8 +36,20 @@ namespace MWWorld { + namespace + { + ESM::CellRef makePlayerCellRef() + { + ESM::CellRef result; + result.blank(); + result.mRefID = ESM::RefId::stringRefId("Player"); + return result; + } + } + Player::Player(const ESM::NPC* player) - : mCellStore(nullptr) + : mPlayer(makePlayerCellRef(), player) + , mCellStore(nullptr) , mLastKnownExteriorPosition(0, 0, 0) , mMarkedPosition(ESM::Position()) , mMarkedCell(nullptr) @@ -46,11 +58,6 @@ namespace MWWorld , mPaidCrimeId(-1) , mJumping(false) { - ESM::CellRef cellRef; - cellRef.blank(); - cellRef.mRefID = ESM::RefId::stringRefId("Player"); - mPlayer = LiveCellRef(cellRef, player); - ESM::Position playerPos = mPlayer.mData.getPosition(); playerPos.pos[0] = playerPos.pos[1] = playerPos.pos[2] = 0; mPlayer.mData.setPosition(playerPos); diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index 97ccfb730a..927f517495 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -287,4 +287,10 @@ namespace ESM loadDataImpl(esm, isDeleted, cellRef); } + CellRef makeBlankCellRef() + { + CellRef result; + result.blank(); + return result; + } } diff --git a/components/esm3/cellref.hpp b/components/esm3/cellref.hpp index 8b23cc5c34..683bde200d 100644 --- a/components/esm3/cellref.hpp +++ b/components/esm3/cellref.hpp @@ -105,6 +105,8 @@ namespace ESM }; void skipLoadCellRef(ESMReader& esm, bool wideRefNum = false); + + CellRef makeBlankCellRef(); } #endif From e01861140e049e49b3b4a9d36dddd20f5fe90712 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 15 Jun 2024 01:10:19 +0200 Subject: [PATCH 11/45] Add tests for MWWorld::Ptr --- apps/openmw_tests/CMakeLists.txt | 1 + apps/openmw_tests/main.cpp | 17 ++++++ apps/openmw_tests/mwworld/testptr.cpp | 82 +++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 apps/openmw_tests/mwworld/testptr.cpp diff --git a/apps/openmw_tests/CMakeLists.txt b/apps/openmw_tests/CMakeLists.txt index 0c6b0d84df..9b57113110 100644 --- a/apps/openmw_tests/CMakeLists.txt +++ b/apps/openmw_tests/CMakeLists.txt @@ -9,6 +9,7 @@ file(GLOB UNITTEST_SRC_FILES mwworld/test_store.cpp mwworld/testduration.cpp mwworld/testtimestamp.cpp + mwworld/testptr.cpp mwdialogue/test_keywordsearch.cpp diff --git a/apps/openmw_tests/main.cpp b/apps/openmw_tests/main.cpp index fd7d4900c8..6b7298596a 100644 --- a/apps/openmw_tests/main.cpp +++ b/apps/openmw_tests/main.cpp @@ -1,11 +1,28 @@ #include +#include +#include +#include #include +#include + int main(int argc, char* argv[]) { Log::sMinDebugLevel = Debug::getDebugLevel(); + const std::filesystem::path settingsDefaultPath = std::filesystem::path{ OPENMW_PROJECT_SOURCE_DIR } / "files" + / Misc::StringUtils::stringToU8String("settings-default.cfg"); + + Settings::SettingsFileParser parser; + parser.loadSettingsFile(settingsDefaultPath, Settings::Manager::mDefaultSettings); + + Settings::StaticValues::initDefaults(); + + Settings::Manager::mUserSettings = Settings::Manager::mDefaultSettings; + + Settings::StaticValues::init(); + testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/apps/openmw_tests/mwworld/testptr.cpp b/apps/openmw_tests/mwworld/testptr.cpp new file mode 100644 index 0000000000..7bc0bebcec --- /dev/null +++ b/apps/openmw_tests/mwworld/testptr.cpp @@ -0,0 +1,82 @@ +#include "apps/openmw/mwclass/npc.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" +#include "apps/openmw/mwworld/livecellref.hpp" +#include "apps/openmw/mwworld/ptr.hpp" +#include "apps/openmw/mwworld/worldmodel.hpp" + +#include +#include + +#include + +namespace MWWorld +{ + namespace + { + using namespace testing; + + TEST(MWWorldPtrTest, toStringShouldReturnHumanReadableTextRepresentationOfPtrWithNullRef) + { + Ptr ptr; + EXPECT_EQ(ptr.toString(), "null object"); + } + + TEST(MWWorldPtrTest, toStringShouldReturnHumanReadableTextRepresentationOfPtrWithDeletedRef) + { + MWClass::Npc::registerSelf(); + ESM::NPC npc; + npc.blank(); + npc.mId = ESM::RefId::stringRefId("Player"); + ESMStore store; + store.insert(npc); + ESM::CellRef cellRef; + cellRef.blank(); + cellRef.mRefID = npc.mId; + cellRef.mRefNum = ESM::RefNum{ .mIndex = 0x2a, .mContentFile = 0xd }; + LiveCellRef liveCellRef(cellRef, &npc); + liveCellRef.mData.setDeletedByContentFile(true); + Ptr ptr(&liveCellRef); + EXPECT_EQ(ptr.toString(), "deleted object0xd00002a (NPC, \"player\")"); + } + + TEST(MWWorldPtrTest, toStringShouldReturnHumanReadableTextRepresentationOfPtr) + { + MWClass::Npc::registerSelf(); + ESM::NPC npc; + npc.blank(); + npc.mId = ESM::RefId::stringRefId("Player"); + ESMStore store; + store.insert(npc); + ESM::CellRef cellRef; + cellRef.blank(); + cellRef.mRefID = npc.mId; + cellRef.mRefNum = ESM::RefNum{ .mIndex = 0x2a, .mContentFile = 0xd }; + LiveCellRef liveCellRef(cellRef, &npc); + Ptr ptr(&liveCellRef); + EXPECT_EQ(ptr.toString(), "object0xd00002a (NPC, \"player\")"); + } + + TEST(MWWorldPtrTest, underlyingLiveCellRefShouldBeDeregisteredOnDestruction) + { + MWClass::Npc::registerSelf(); + ESM::NPC npc; + npc.blank(); + npc.mId = ESM::RefId::stringRefId("Player"); + ESMStore store; + store.insert(npc); + ESM::ReadersCache readersCache; + WorldModel worldModel(store, readersCache); + ESM::CellRef cellRef; + cellRef.blank(); + cellRef.mRefID = npc.mId; + cellRef.mRefNum = ESM::FormId{ .mIndex = 0x2a, .mContentFile = 0xd }; + { + LiveCellRef liveCellRef(cellRef, &npc); + Ptr ptr(&liveCellRef); + worldModel.registerPtr(ptr); + ASSERT_EQ(worldModel.getPtr(cellRef.mRefNum), ptr); + } + EXPECT_EQ(worldModel.getPtr(cellRef.mRefNum), Ptr()); + } + } +} From 1ef8d0570bcae1b4b2b26637c403b96bf4d2d3b8 Mon Sep 17 00:00:00 2001 From: phenine <99gs3fnr@anonaddy.me> Date: Fri, 6 Sep 2024 00:45:08 +0800 Subject: [PATCH 12/45] `openmw.ambient` grammar, punctuation and wording tweaks --- files/lua_api/openmw/ambient.lua | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/files/lua_api/openmw/ambient.lua b/files/lua_api/openmw/ambient.lua index ff776f84fb..af854510a1 100644 --- a/files/lua_api/openmw/ambient.lua +++ b/files/lua_api/openmw/ambient.lua @@ -1,6 +1,6 @@ --- --- `openmw.ambient` controls background sounds, specific to given player (2D-sounds). --- Can be used only by menu scripts and local scripts, that are attached to a player. +-- `openmw.ambient` controls background 2D sounds specific to a given player. +-- Can be used only by menu scripts and local scripts that are attached to a player. -- @module ambient -- @usage local ambient = require('openmw.ambient') @@ -12,11 +12,11 @@ -- @param #string soundId ID of Sound record to play -- @param #table options An optional table with additional optional arguments. Can contain: -- --- * `timeOffset` - a floating point number >= 0, to skip some time (in seconds) from beginning of sound file (default: 0); --- * `volume` - a floating point number >= 0, to set a sound volume (default: 1); --- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1); --- * `scale` - a boolean, to set if sound pitch should be scaled by simulation time scaling (default: true); --- * `loop` - a boolean, to set if sound should be repeated when it ends (default: false); +-- * `timeOffset` - a floating point number >= 0, to skip some time (in seconds) from the beginning of the sound (default: 0); +-- * `volume` - a floating point number >= 0, to set the sound's volume (default: 1); +-- * `pitch` - a floating point number >= 0, to set the sound's pitch (default: 1); +-- * `scale` - a boolean, to set if the sound's pitch should be scaled by simulation time scaling (default: true); +-- * `loop` - a boolean, to set if the sound should be repeated when it ends (default: false); -- @usage local params = { -- timeOffset=0.1 -- volume=0.3, @@ -29,14 +29,14 @@ --- -- Play a 2D sound file -- @function [parent=#ambient] playSoundFile --- @param #string fileName Path to sound file in VFS +-- @param #string fileName Path to a sound file in VFS -- @param #table options An optional table with additional optional arguments. Can contain: -- --- * `timeOffset` - a floating point number >= 0, to skip some time (in seconds) from beginning of sound file (default: 0); --- * `volume` - a floating point number >= 0, to set a sound volume (default: 1); --- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1); --- * `scale` - a boolean, to set if sound pitch should be scaled by simulation time scaling (default: true); --- * `loop` - a boolean, to set if sound should be repeated when it ends (default: false); +-- * `timeOffset` - a floating point number >= 0, to skip some time (in seconds) from the beginning of the sound file (default: 0); +-- * `volume` - a floating point number >= 0, to set a the sound's volume (default: 1); +-- * `pitch` - a floating point number >= 0, to set a the sound's pitch (default: 1); +-- * `scale` - a boolean, to set if the sound's pitch should be scaled by simulation time scaling (default: true); +-- * `loop` - a boolean, to set if the sound should be repeated when it ends (default: false); -- @usage local params = { -- timeOffset=0.1 -- volume=0.3, @@ -55,37 +55,37 @@ --- -- Stop a sound file -- @function [parent=#ambient] stopSoundFile --- @param #string fileName Path to sound file in VFS +-- @param #string fileName Path to a sound file in VFS -- @usage ambient.stopSoundFile("Sound\\test.mp3"); --- --- Check if sound is playing +-- Check if a sound is playing -- @function [parent=#ambient] isSoundPlaying -- @param #string soundId ID of Sound record to check -- @return #boolean -- @usage local isPlaying = ambient.isSoundPlaying("shock bolt"); --- --- Check if sound file is playing +-- Check if a sound file is playing -- @function [parent=#ambient] isSoundFilePlaying --- @param #string fileName Path to sound file in VFS +-- @param #string fileName Path to a sound file in VFS -- @return #boolean -- @usage local isPlaying = ambient.isSoundFilePlaying("Sound\\test.mp3"); --- -- Play a sound file as a music track -- @function [parent=#ambient] streamMusic --- @param #string fileName Path to file in VFS +-- @param #string fileName Path to a file in VFS -- @param #table options An optional table with additional optional arguments. Can contain: -- --- * `fadeOut` - a floating point number >= 0, time (in seconds) to fade out current track before playing this one (default 1.0); +-- * `fadeOut` - a floating point number >= 0, time (in seconds) to fade out the current track before playing this one (default 1.0); -- @usage local params = { -- fadeOut=2.0 -- }; -- ambient.streamMusic("Music\\Test\\Test.mp3", params) --- --- Stop to play current music +-- Stop the currently playing music -- @function [parent=#ambient] stopMusic -- @usage ambient.stopMusic(); @@ -98,7 +98,7 @@ --- -- Play an ambient voiceover. -- @function [parent=#ambient] say --- @param #string fileName Path to sound file in VFS +-- @param #string fileName Path to a sound file in VFS -- @param #string text Subtitle text (optional) -- @usage -- play voiceover and print messagebox -- ambient.say("Sound\\Vo\\Misc\\voice.mp3", "Subtitle text") @@ -108,7 +108,7 @@ --- -- Stop an ambient voiceover -- @function [parent=#ambient] stopSay --- @param #string fileName Path to sound file in VFS +-- @param #string fileName Path to a sound file in VFS -- @usage ambient.stopSay(); --- From 3df183460d9c3e4b9a16bdf0dd10ae173a613c77 Mon Sep 17 00:00:00 2001 From: phenine <99gs3fnr@anonaddy.me> Date: Fri, 6 Sep 2024 01:01:38 +0800 Subject: [PATCH 13/45] `openmw.animation` minor punctuation. --- files/lua_api/openmw/animation.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/lua_api/openmw/animation.lua b/files/lua_api/openmw/animation.lua index ba57708a94..065a150605 100644 --- a/files/lua_api/openmw/animation.lua +++ b/files/lua_api/openmw/animation.lua @@ -1,5 +1,5 @@ --- --- `openmw.animation` defines functions that allow control of character animations +-- `openmw.animation` defines functions that allow control of character animations. -- Note that for some methods, such as @{openmw.animation#playBlended} you should use the associated methods on the -- [AnimationController](interface_animation.html) interface rather than invoking this API directly. -- @module animation @@ -55,7 +55,7 @@ -- @return #boolean --- --- Skips animations for one frame, equivalent to mwscript's SkipAnim +-- Skips animations for one frame, equivalent to mwscript's SkipAnim. -- Can be used only in local scripts on self. -- @function [parent=#animation] skipAnimationThisFrame -- @param openmw.core#GameObject actor @@ -98,7 +98,7 @@ --- --- Cancels and removes the animation group from the list of active animations +-- Cancels and removes the animation group from the list of active animations. -- Can be used only in local scripts on self. -- @function [parent=#animation] cancel -- @param openmw.core#GameObject actor From c1165815dff66bf9e0205873d7c2c9cd5da03822 Mon Sep 17 00:00:00 2001 From: phenine <99gs3fnr@anonaddy.me> Date: Fri, 6 Sep 2024 01:07:29 +0800 Subject: [PATCH 14/45] `openmw.async` wording and grammar. --- files/lua_api/openmw/async.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/files/lua_api/openmw/async.lua b/files/lua_api/openmw/async.lua index 64080ed96a..7a410336c0 100644 --- a/files/lua_api/openmw/async.lua +++ b/files/lua_api/openmw/async.lua @@ -1,5 +1,5 @@ --- --- `openmw.async` contains timers and coroutine utils. All functions require +-- `openmw.async` contains timers and coroutine utilities. All functions require -- the package itself as a first argument. -- @module async -- @usage local async = require('openmw.async') @@ -16,7 +16,7 @@ --- -- Calls callback(arg) in `delay` simulation seconds. --- Callback must be registered in advance. +-- The callback must be registered in advance. -- @function [parent=#async] newSimulationTimer -- @param self -- @param #number delay @@ -25,7 +25,7 @@ --- -- Calls callback(arg) in `delay` game seconds. --- Callback must be registered in advance. +-- The callback must be registered in advance. -- @function [parent=#async] newGameTimer -- @param self -- @param #number delay @@ -49,7 +49,7 @@ -- @param #function func --- --- Wraps Lua function with `Callback` object that can be used in async API calls. +-- Wraps a Lua function with a `Callback` object that can be used in async API calls. -- @function [parent=#async] callback -- @param self -- @param #function func From 14868d4b568f6817fec20e4acd5d8c9aefb67bdc Mon Sep 17 00:00:00 2001 From: phenine <99gs3fnr@anonaddy.me> Date: Fri, 6 Sep 2024 02:02:33 +0800 Subject: [PATCH 15/45] `openmw.ambient` fix my own stupid mistake --- files/lua_api/openmw/ambient.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/lua_api/openmw/ambient.lua b/files/lua_api/openmw/ambient.lua index af854510a1..82d4dfdeee 100644 --- a/files/lua_api/openmw/ambient.lua +++ b/files/lua_api/openmw/ambient.lua @@ -33,8 +33,8 @@ -- @param #table options An optional table with additional optional arguments. Can contain: -- -- * `timeOffset` - a floating point number >= 0, to skip some time (in seconds) from the beginning of the sound file (default: 0); --- * `volume` - a floating point number >= 0, to set a the sound's volume (default: 1); --- * `pitch` - a floating point number >= 0, to set a the sound's pitch (default: 1); +-- * `volume` - a floating point number >= 0, to set the sound's volume (default: 1); +-- * `pitch` - a floating point number >= 0, to set the sound's pitch (default: 1); -- * `scale` - a boolean, to set if the sound's pitch should be scaled by simulation time scaling (default: true); -- * `loop` - a boolean, to set if the sound should be repeated when it ends (default: false); -- @usage local params = { From 82cd151b4c331ffd271dc727fc7d1726fc31c567 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 16 Sep 2024 16:38:26 +0200 Subject: [PATCH 16/45] Start combat music for fleeing actors that haven't drawn a weapon --- files/data/scripts/omw/music/actor.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/music/actor.lua b/files/data/scripts/omw/music/actor.lua index 4890387e54..8f3ac7915a 100755 --- a/files/data/scripts/omw/music/actor.lua +++ b/files/data/scripts/omw/music/actor.lua @@ -24,7 +24,7 @@ local function onUpdate() -- Early-out for actors without targets and without combat state -- TODO: use events or engine handlers to detect when targets change local isStanceNothing = types.Actor.getStance(self) == types.Actor.STANCE.Nothing - if isStanceNothing and next(targets) == nil then + if isStanceNothing and next(targets) == nil and not AI.isFleeing() then return end From 2ddc77138a9c1a9620e3eb97e8e8fe2f7db9e1cd Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 18 Sep 2024 23:52:20 +0200 Subject: [PATCH 17/45] Use normalized path in ESM4 reader --- components/esm4/reader.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index 2d9a929bb2..505922601d 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -320,16 +320,18 @@ namespace ESM4 std::filesystem::path path = strings / (prefix + language + suffix); if (mVFS != nullptr) { - std::string vfsPath = Files::pathToUnicodeString(path); - if (!mVFS->exists(vfsPath)) + VFS::Path::Normalized vfsPath(Files::pathToUnicodeString(path)); + Files::IStreamPtr stream = mVFS->find(vfsPath); + + if (stream == nullptr) { path = strings / (prefix + altLanguage + suffix); - vfsPath = Files::pathToUnicodeString(path); + vfsPath = VFS::Path::Normalized(Files::pathToUnicodeString(path)); + stream = mVFS->find(vfsPath); } - if (mVFS->exists(vfsPath)) + if (stream != nullptr) { - const Files::IStreamPtr stream = mVFS->get(vfsPath); buildLStringIndex(stringType, *stream); return; } From 597d1853eebef71fd410111de63ba81ebce22661 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 18 Sep 2024 23:52:44 +0200 Subject: [PATCH 18/45] Use normalized path in ESM LuaScripts --- components/esm/luascripts.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esm/luascripts.cpp b/components/esm/luascripts.cpp index 71e2ce6dc1..81cc867aff 100644 --- a/components/esm/luascripts.cpp +++ b/components/esm/luascripts.cpp @@ -56,7 +56,7 @@ void ESM::LuaScriptsCfg::load(ESMReader& esm) { mScripts.emplace_back(); ESM::LuaScriptCfg& script = mScripts.back(); - script.mScriptPath = esm.getHString(); + script.mScriptPath = VFS::Path::Normalized(esm.getHString()); esm.getSubNameIs("LUAF"); esm.getSubHeader(); @@ -161,7 +161,7 @@ void ESM::LuaScripts::load(ESMReader& esm) { while (esm.isNextSub("LUAS")) { - std::string name = esm.getHString(); + VFS::Path::Normalized name(esm.getHString()); std::string data = loadLuaBinaryData(esm); std::vector timers; while (esm.isNextSub("LUAT")) From 85edc49f2f843b613760d4e836de769f5a671579 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 18 Sep 2024 23:53:04 +0200 Subject: [PATCH 19/45] Remove unused argument --- components/misc/resourcehelpers.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 5ed0bcc972..1728e51c7a 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -228,7 +228,7 @@ bool Misc::ResourceHelpers::isHiddenMarker(const ESM::RefId& id) namespace { - std::string getLODMeshNameImpl(std::string resPath, const VFS::Manager* vfs, std::string_view pattern) + std::string getLODMeshNameImpl(std::string resPath, std::string_view pattern) { if (auto w = Misc::findExtension(resPath); w != std::string::npos) resPath.insert(w, pattern); @@ -237,7 +237,7 @@ namespace std::string getBestLODMeshName(std::string const& resPath, const VFS::Manager* vfs, std::string_view pattern) { - if (const auto& result = getLODMeshNameImpl(resPath, vfs, pattern); vfs->exists(result)) + if (std::string result = getLODMeshNameImpl(resPath, pattern); vfs->exists(result)) return result; return resPath; } From a78f5182e48443054ab54eff27345add1cf85c77 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 18 Sep 2024 23:53:17 +0200 Subject: [PATCH 20/45] Replace toNormalized by Normalized fname type cannot be changed because it's part of mygui library interface. --- components/myguiplatform/myguitexture.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp index fe2b700049..9d865e1296 100644 --- a/components/myguiplatform/myguitexture.cpp +++ b/components/myguiplatform/myguitexture.cpp @@ -95,7 +95,7 @@ namespace osgMyGUI if (!mImageManager) throw std::runtime_error("No imagemanager set"); - osg::ref_ptr image(mImageManager->getImage(VFS::Path::toNormalized(fname))); + osg::ref_ptr image(mImageManager->getImage(VFS::Path::Normalized(fname))); mTexture = new osg::Texture2D(image); mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); From f1533c215dce038592bfefeda549b190d989f99a Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 18 Sep 2024 23:56:03 +0200 Subject: [PATCH 21/45] Use normalized path in ActorAnimation::attach --- apps/openmw/mwrender/actoranimation.cpp | 5 ++--- apps/openmw/mwrender/actoranimation.hpp | 4 +++- apps/openmw/mwrender/creatureanimation.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 35ff81a9ca..df712b43b0 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -88,10 +88,9 @@ namespace MWRender } osg::ref_ptr ActorAnimation::attach( - const std::string& model, std::string_view bonename, std::string_view bonefilter, bool isLight) + VFS::Path::NormalizedView model, std::string_view bonename, std::string_view bonefilter, bool isLight) { - osg::ref_ptr templateNode - = mResourceSystem->getSceneManager()->getTemplate(VFS::Path::toNormalized(model)); + osg::ref_ptr templateNode = mResourceSystem->getSceneManager()->getTemplate(model); const NodeMap& nodeMap = getNodeMap(); auto found = nodeMap.find(bonename); diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index b6586d4eab..5a28c41b6c 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -5,6 +5,8 @@ #include +#include + #include "../mwworld/containerstore.hpp" #include "animation.hpp" @@ -59,7 +61,7 @@ namespace MWRender return attachMesh(model, bonename, false, &stubColor); } osg::ref_ptr attach( - const std::string& model, std::string_view bonename, std::string_view bonefilter, bool isLight); + VFS::Path::NormalizedView model, std::string_view bonename, std::string_view bonefilter, bool isLight); PartHolderPtr mScabbard; PartHolderPtr mHolsteredShield; diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index b6cc823d28..f84e58e4bc 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -111,7 +111,7 @@ namespace MWRender MWWorld::ConstPtr item = *it; std::string_view bonename; - std::string itemModel = item.getClass().getCorrectedModel(item); + VFS::Path::Normalized itemModel = item.getClass().getCorrectedModel(item); if (slot == MWWorld::InventoryStore::Slot_CarriedRight) { if (item.getType() == ESM::Weapon::sRecordId) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 08dfcb667c..c8fc36e18a 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -681,11 +681,11 @@ namespace MWRender PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, std::string_view bonename, std::string_view bonefilter, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight) { - osg::ref_ptr attached = attach(model, bonename, bonefilter, isLight); + osg::ref_ptr attached = attach(VFS::Path::toNormalized(model), bonename, bonefilter, isLight); if (enchantedGlow) mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor); - return std::make_unique(attached); + return std::make_unique(std::move(attached)); } osg::Vec3f NpcAnimation::runAnimation(float timepassed) From d7e99f988b6e8633f0bb21f12e08978b72f02760 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 19 Sep 2024 00:07:36 +0200 Subject: [PATCH 22/45] Use normalized path in ActorAnimation::attachMesh --- apps/openmw/mwrender/actoranimation.cpp | 25 +++++++++++++------------ apps/openmw/mwrender/actoranimation.hpp | 9 ++++++--- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index df712b43b0..123090209f 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -67,14 +67,13 @@ namespace MWRender } PartHolderPtr ActorAnimation::attachMesh( - const std::string& model, std::string_view bonename, bool enchantedGlow, osg::Vec4f* glowColor) + VFS::Path::NormalizedView model, std::string_view bonename, bool enchantedGlow, osg::Vec4f* glowColor) { osg::Group* parent = getBoneByName(bonename); if (!parent) return nullptr; - osg::ref_ptr instance - = mResourceSystem->getSceneManager()->getInstance(VFS::Path::toNormalized(model), parent); + osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model, parent); const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(bonename); @@ -217,13 +216,13 @@ namespace MWRender return; } - std::string mesh = getSheathedShieldMesh(*shield); + const VFS::Path::Normalized mesh = getSheathedShieldMesh(*shield); if (mesh.empty()) return; - std::string_view boneName = "Bip01 AttachShield"; + constexpr std::string_view boneName = "Bip01 AttachShield"; osg::Vec4f glowColor = shield->getClass().getEnchantmentColor(*shield); - const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); + const VFS::Path::Normalized holsteredName = addSuffixBeforeExtension(mesh, "_sh"); bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty(); // If we have no dedicated sheath model, use basic shield model as fallback. @@ -244,8 +243,7 @@ namespace MWRender // file. if (shieldNode && !shieldNode->getNumChildren()) { - osg::ref_ptr fallbackNode - = mResourceSystem->getSceneManager()->getInstance(VFS::Path::toNormalized(mesh), shieldNode); + osg::ref_ptr fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, shieldNode); if (isEnchanted) SceneUtil::addEnchantedGlow(shieldNode, mResourceSystem, glowColor); } @@ -340,13 +338,16 @@ namespace MWRender if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) showHolsteredWeapons = false; - std::string mesh = weapon->getClass().getCorrectedModel(*weapon); - std::string_view boneName = getHolsteredWeaponBoneName(*weapon); - if (mesh.empty() || boneName.empty()) + const VFS::Path::Normalized mesh = weapon->getClass().getCorrectedModel(*weapon); + if (mesh.empty()) + return; + + const std::string_view boneName = getHolsteredWeaponBoneName(*weapon); + if (boneName.empty()) return; // If the scabbard is not found, use the weapon mesh as fallback. - const std::string scabbardName = addSuffixBeforeExtension(mesh, "_sh"); + const VFS::Path::Normalized scabbardName = addSuffixBeforeExtension(mesh, "_sh"); bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); if (!mResourceSystem->getVFS()->exists(scabbardName)) { diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index 5a28c41b6c..ba10c4fc16 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -53,13 +53,16 @@ namespace MWRender std::string getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const; virtual std::string getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const; virtual std::string_view getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon); - virtual PartHolderPtr attachMesh( - const std::string& model, std::string_view bonename, bool enchantedGlow, osg::Vec4f* glowColor); - virtual PartHolderPtr attachMesh(const std::string& model, std::string_view bonename) + + PartHolderPtr attachMesh( + VFS::Path::NormalizedView model, std::string_view bonename, bool enchantedGlow, osg::Vec4f* glowColor); + + PartHolderPtr attachMesh(VFS::Path::NormalizedView model, std::string_view bonename) { osg::Vec4f stubColor = osg::Vec4f(0, 0, 0, 0); return attachMesh(model, bonename, false, &stubColor); } + osg::ref_ptr attach( VFS::Path::NormalizedView model, std::string_view bonename, std::string_view bonefilter, bool isLight); From 8ba4ff99468d88d054c581262b19b3668f49a709 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 19 Sep 2024 00:13:41 +0200 Subject: [PATCH 23/45] Remove redundant bool argument from ActorAnimation::attachMesh --- apps/openmw/mwrender/actoranimation.cpp | 19 ++++++++++--------- apps/openmw/mwrender/actoranimation.hpp | 8 +------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 123090209f..96141c9ebf 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -67,7 +67,7 @@ namespace MWRender } PartHolderPtr ActorAnimation::attachMesh( - VFS::Path::NormalizedView model, std::string_view bonename, bool enchantedGlow, osg::Vec4f* glowColor) + VFS::Path::NormalizedView model, std::string_view bonename, const osg::Vec4f* glowColor) { osg::Group* parent = getBoneByName(bonename); if (!parent) @@ -80,7 +80,7 @@ namespace MWRender if (found == nodeMap.end()) return {}; - if (enchantedGlow) + if (glowColor != nullptr) mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor); return std::make_unique(instance); @@ -221,15 +221,15 @@ namespace MWRender return; constexpr std::string_view boneName = "Bip01 AttachShield"; - osg::Vec4f glowColor = shield->getClass().getEnchantmentColor(*shield); + const bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty(); + const osg::Vec4f glowColor = isEnchanted ? shield->getClass().getEnchantmentColor(*shield) : osg::Vec4f(); const VFS::Path::Normalized holsteredName = addSuffixBeforeExtension(mesh, "_sh"); - bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty(); // If we have no dedicated sheath model, use basic shield model as fallback. if (!mResourceSystem->getVFS()->exists(holsteredName)) - mHolsteredShield = attachMesh(mesh, boneName, isEnchanted, &glowColor); + mHolsteredShield = attachMesh(mesh, boneName, isEnchanted ? &glowColor : nullptr); else - mHolsteredShield = attachMesh(holsteredName, boneName, isEnchanted, &glowColor); + mHolsteredShield = attachMesh(holsteredName, boneName, isEnchanted ? &glowColor : nullptr); if (!mHolsteredShield) return; @@ -348,13 +348,14 @@ namespace MWRender // If the scabbard is not found, use the weapon mesh as fallback. const VFS::Path::Normalized scabbardName = addSuffixBeforeExtension(mesh, "_sh"); - bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); + const bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); if (!mResourceSystem->getVFS()->exists(scabbardName)) { if (showHolsteredWeapons) { - osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); - mScabbard = attachMesh(mesh, boneName, isEnchanted, &glowColor); + const osg::Vec4f glowColor + = isEnchanted ? weapon->getClass().getEnchantmentColor(*weapon) : osg::Vec4f(); + mScabbard = attachMesh(mesh, boneName, isEnchanted ? &glowColor : nullptr); if (mScabbard) resetControllers(mScabbard->getNode()); } diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index ba10c4fc16..04a7ada323 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -55,13 +55,7 @@ namespace MWRender virtual std::string_view getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon); PartHolderPtr attachMesh( - VFS::Path::NormalizedView model, std::string_view bonename, bool enchantedGlow, osg::Vec4f* glowColor); - - PartHolderPtr attachMesh(VFS::Path::NormalizedView model, std::string_view bonename) - { - osg::Vec4f stubColor = osg::Vec4f(0, 0, 0, 0); - return attachMesh(model, bonename, false, &stubColor); - } + VFS::Path::NormalizedView model, std::string_view bonename, const osg::Vec4f* glowColor = nullptr); osg::ref_ptr attach( VFS::Path::NormalizedView model, std::string_view bonename, std::string_view bonefilter, bool isLight); From 320d376b95c224216ddc942ac48483040f6c8953 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 19 Sep 2024 00:17:44 +0200 Subject: [PATCH 24/45] Remove unused virtual specifier --- apps/openmw/mwrender/actoranimation.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index 04a7ada323..0182df9370 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -47,12 +47,12 @@ namespace MWRender protected: osg::Group* getBoneByName(std::string_view boneName) const; - virtual void updateHolsteredWeapon(bool showHolsteredWeapons); - virtual void updateHolsteredShield(bool showCarriedLeft); - virtual void updateQuiver(); + void updateHolsteredWeapon(bool showHolsteredWeapons); + void updateHolsteredShield(bool showCarriedLeft); + void updateQuiver(); std::string getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const; virtual std::string getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const; - virtual std::string_view getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon); + std::string_view getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon); PartHolderPtr attachMesh( VFS::Path::NormalizedView model, std::string_view bonename, const osg::Vec4f* glowColor = nullptr); From 3475a166e50ae6e0991b953e1bb534056ae0d390 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 19 Sep 2024 00:31:16 +0200 Subject: [PATCH 25/45] Use normalized path for World::spawnEffect --- apps/openmw/mwbase/world.hpp | 4 ++-- apps/openmw/mwlua/animationbindings.cpp | 6 +++--- apps/openmw/mwmechanics/actors.cpp | 5 +++-- apps/openmw/mwmechanics/spellcasting.cpp | 10 ++++++---- apps/openmw/mwworld/worldimp.cpp | 6 +++--- apps/openmw/mwworld/worldimp.hpp | 5 +++-- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 6ab5ab64fa..904b96c463 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -4,13 +4,13 @@ #include "rotationflags.hpp" #include -#include #include #include #include #include #include +#include #include "../mwworld/doorstate.hpp" #include "../mwworld/globalvariablename.hpp" @@ -515,7 +515,7 @@ namespace MWBase /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect(const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0; - virtual void spawnEffect(const std::string& model, const std::string& textureOverride, + virtual void spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) = 0; diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index 95294f46cb..b74a3e51c3 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -319,14 +319,14 @@ namespace MWLua std::string texture = options->get_or("particleTextureOverride", ""); float scale = options->get_or("scale", 1.f); context.mLuaManager->addAction( - [world, model = std::string(model), texture = std::move(texture), worldPos, scale, + [world, model = VFS::Path::Normalized(model), texture = std::move(texture), worldPos, scale, magicVfx]() { world->spawnEffect(model, texture, worldPos, scale, magicVfx); }, "openmw.vfx.spawn"); } else { - context.mLuaManager->addAction( - [world, model = std::string(model), worldPos]() { world->spawnEffect(model, "", worldPos); }, + context.mLuaManager->addAction([world, model = VFS::Path::Normalized(model), + worldPos]() { world->spawnEffect(model, "", worldPos); }, "openmw.vfx.spawn"); } }; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3f2a9b46bb..401ba0ae86 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -214,7 +214,7 @@ namespace const ESM::Static* const fx = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Soul_Trap")); if (fx != nullptr) - world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), "", + world->spawnEffect(VFS::Path::toNormalized(Misc::ResourceHelpers::correctMeshPath(fx->mModel)), "", creature.getRefData().getPosition().asVec3()); MWBase::Environment::get().getSoundManager()->playSound3D( @@ -1806,7 +1806,8 @@ namespace MWMechanics ESM::RefId::stringRefId("VFX_Summon_End")); if (fx) MWBase::Environment::get().getWorld()->spawnEffect( - Misc::ResourceHelpers::correctMeshPath(fx->mModel), "", ptr.getRefData().getPosition().asVec3()); + VFS::Path::toNormalized(Misc::ResourceHelpers::correctMeshPath(fx->mModel)), "", + ptr.getRefData().getPosition().asVec3()); // Remove the summoned creature's summoned creatures as well MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d22b6c4837..1d847a4129 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -72,12 +72,13 @@ namespace MWMechanics { if (effectInfo.mData.mRange == ESM::RT_Target) world->spawnEffect( - Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, 1.0f); + VFS::Path::toNormalized(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel)), texture, + mHitPosition, 1.0f); continue; } else - world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, - static_cast(effectInfo.mData.mArea * 2)); + world->spawnEffect(VFS::Path::toNormalized(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel)), + texture, mHitPosition, static_cast(effectInfo.mData.mArea * 2)); // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) { @@ -539,7 +540,8 @@ namespace MWMechanics } scale = std::max(scale, 1.f); MWBase::Environment::get().getWorld()->spawnEffect( - Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), effect->mParticle, pos, scale); + VFS::Path::toNormalized(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel)), + effect->mParticle, pos, scale); } if (animation && !mCaster.getClass().isActor()) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b52d59d5e7..07da51b4f3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3644,10 +3644,10 @@ namespace MWWorld mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false); } - void World::spawnEffect(const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, - float scale, bool isMagicVFX) + void World::spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride, + const osg::Vec3f& worldPos, float scale, bool isMagicVFX) { - mRendering->spawnEffect(VFS::Path::toNormalized(model), textureOverride, worldPos, scale, isMagicVFX); + mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX); } struct ResetActorsVisitor diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index f4c22e94d3..9e00118533 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "../mwbase/world.hpp" @@ -603,8 +604,8 @@ namespace MWWorld /// Spawn a blood effect for \a ptr at \a worldPosition void spawnBloodEffect(const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override; - void spawnEffect(const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, - float scale = 1.f, bool isMagicVFX = true) override; + void spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride, + const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) override; /// @see MWWorld::WeatherManager::isInStorm bool isInStorm() const override; From 28faae69b0ba88270003786a8dfa942175f4f497 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 19 Sep 2024 00:36:45 +0200 Subject: [PATCH 26/45] Use normalized path in TextureManager --- components/terrain/defs.hpp | 6 +++--- components/terrain/texturemanager.cpp | 22 ++++++++++------------ components/terrain/texturemanager.hpp | 5 ++--- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp index c2342c50d2..48e4813e65 100644 --- a/components/terrain/defs.hpp +++ b/components/terrain/defs.hpp @@ -1,7 +1,7 @@ #ifndef COMPONENTS_TERRAIN_DEFS_HPP #define COMPONENTS_TERRAIN_DEFS_HPP -#include +#include namespace Terrain { @@ -16,8 +16,8 @@ namespace Terrain struct LayerInfo { - std::string mDiffuseMap; - std::string mNormalMap; + VFS::Path::Normalized mDiffuseMap; + VFS::Path::Normalized mNormalMap; bool mParallax; // Height info in normal map alpha channel? bool mSpecular; // Specular info in diffuse map alpha channel? diff --git a/components/terrain/texturemanager.cpp b/components/terrain/texturemanager.cpp index 8df880fab0..06f3243330 100644 --- a/components/terrain/texturemanager.cpp +++ b/components/terrain/texturemanager.cpp @@ -35,23 +35,21 @@ namespace Terrain mCache->call(f); } - osg::ref_ptr TextureManager::getTexture(const std::string& name) + osg::ref_ptr TextureManager::getTexture(VFS::Path::NormalizedView name) { // don't bother with case folding, since there is only one way of referring to terrain textures we can assume // the case is always the same osg::ref_ptr obj = mCache->getRefFromObjectCache(name); - if (obj) + + if (obj != nullptr) return static_cast(obj.get()); - else - { - osg::ref_ptr texture( - new osg::Texture2D(mSceneManager->getImageManager()->getImage(VFS::Path::toNormalized(name)))); - texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); - texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - mSceneManager->applyFilterSettings(texture); - mCache->addEntryToObjectCache(name, texture.get()); - return texture; - } + + osg::ref_ptr texture(new osg::Texture2D(mSceneManager->getImageManager()->getImage(name))); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + mSceneManager->applyFilterSettings(texture); + mCache->addEntryToObjectCache(name.value(), texture.get()); + return texture; } void TextureManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const diff --git a/components/terrain/texturemanager.hpp b/components/terrain/texturemanager.hpp index 96fb43abfa..a79ef53b88 100644 --- a/components/terrain/texturemanager.hpp +++ b/components/terrain/texturemanager.hpp @@ -1,9 +1,8 @@ #ifndef OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H #define OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H -#include - #include +#include namespace Resource { @@ -25,7 +24,7 @@ namespace Terrain void updateTextureFiltering(); - osg::ref_ptr getTexture(const std::string& name); + osg::ref_ptr getTexture(VFS::Path::NormalizedView name); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; From 5f2582fe6856db5028a8f626e11e9f83daa88767 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 19 Sep 2024 00:47:51 +0200 Subject: [PATCH 27/45] Use normalized path in SceneManager::checkLoaded --- apps/openmw/mwworld/scene.cpp | 24 ++++++++++++------------ components/resource/scenemanager.cpp | 4 ++-- components/resource/scenemanager.hpp | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 66aa027a89..c42a786936 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1126,19 +1126,19 @@ namespace MWWorld void Scene::preload(const std::string& mesh, bool useAnim) { - std::string meshPath = mesh; - if (useAnim) - meshPath = Misc::ResourceHelpers::correctActorModelPath(meshPath, mRendering.getResourceSystem()->getVFS()); + const VFS::Path::Normalized meshPath = useAnim + ? Misc::ResourceHelpers::correctActorModelPath(mesh, mRendering.getResourceSystem()->getVFS()) + : mesh; - if (!mRendering.getResourceSystem()->getSceneManager()->checkLoaded(meshPath, mRendering.getReferenceTime())) - { - osg::ref_ptr item(new PreloadMeshItem( - VFS::Path::toNormalized(meshPath), mRendering.getResourceSystem()->getSceneManager())); - mRendering.getWorkQueue()->addWorkItem(item); - const auto isDone = [](const osg::ref_ptr& v) { return v->isDone(); }; - mWorkItems.erase(std::remove_if(mWorkItems.begin(), mWorkItems.end(), isDone), mWorkItems.end()); - mWorkItems.emplace_back(std::move(item)); - } + if (mRendering.getResourceSystem()->getSceneManager()->checkLoaded(meshPath, mRendering.getReferenceTime())) + return; + + osg::ref_ptr item( + new PreloadMeshItem(meshPath, mRendering.getResourceSystem()->getSceneManager())); + mRendering.getWorkQueue()->addWorkItem(item); + const auto isDone = [](const osg::ref_ptr& v) { return v->isDone(); }; + mWorkItems.erase(std::remove_if(mWorkItems.begin(), mWorkItems.end(), isDone), mWorkItems.end()); + mWorkItems.emplace_back(std::move(item)); } void Scene::preloadCells(float dt) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 9feab669a9..82a77ccc0a 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -597,9 +597,9 @@ namespace Resource mShaderManager->setShaderPath(path); } - bool SceneManager::checkLoaded(const std::string& name, double timeStamp) + bool SceneManager::checkLoaded(VFS::Path::NormalizedView name, double timeStamp) { - return mCache->checkInObjectCache(VFS::Path::normalizeFilename(name), timeStamp); + return mCache->checkInObjectCache(name, timeStamp); } void SceneManager::setUpNormalsRTForStateSet(osg::StateSet* stateset, bool enabled) diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 6ea94233e7..ecd94e257c 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -152,7 +152,7 @@ namespace Resource void setShaderPath(const std::filesystem::path& path); /// Check if a given scene is loaded and if so, update its usage timestamp to prevent it from being unloaded - bool checkLoaded(const std::string& name, double referenceTime); + bool checkLoaded(VFS::Path::NormalizedView name, double referenceTime); /// Get a read-only copy of this scene "template" /// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead. From 7e453d491a573de3429238c218bda8dc4cd4529b Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 19 Sep 2024 00:53:27 +0200 Subject: [PATCH 28/45] Remove redundant toNormalized --- apps/openmw/mwrender/animation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 5b7d1db78a..64e961e22e 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1508,10 +1508,10 @@ namespace MWRender } animationPath.replace(animationPath.size() - 4, 4, "/"); - for (const auto& name : resourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) + for (const VFS::Path::Normalized& name : resourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { if (Misc::getFileExtension(name) == "nif") - loadBonesFromFile(node, VFS::Path::toNormalized(name), resourceSystem); + loadBonesFromFile(node, name, resourceSystem); } } From 2ef5a8486d417312ff3c8b97f0d75d2ec61298d1 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 19 Sep 2024 01:05:10 +0200 Subject: [PATCH 29/45] Use normalized path in ObjectPaging::createChunk --- apps/openmw/mwrender/objectpaging.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 6e39d99404..8040790bd7 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -639,7 +640,7 @@ namespace MWRender continue; const int type = store.findStatic(ref.mRefId); - std::string model = getModel(type, ref.mRefId, store); + VFS::Path::Normalized model = getModel(type, ref.mRefId, store); if (model.empty()) continue; model = Misc::ResourceHelpers::correctMeshPath(model); @@ -647,10 +648,10 @@ namespace MWRender if (activeGrid && type != ESM::REC_STAT) { model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS()); - std::string kfname = Misc::StringUtils::lowerCase(model); - if (kfname.size() > 4 && kfname.ends_with(".nif")) + if (Misc::getFileExtension(model) == "nif") { - kfname.replace(kfname.size() - 4, 4, ".kf"); + VFS::Path::Normalized kfname = model; + kfname.changeExtension("kf"); if (mSceneManager->getVFS()->exists(kfname)) continue; } @@ -671,7 +672,7 @@ namespace MWRender ->second; } - osg::ref_ptr cnode = mSceneManager->getTemplate(VFS::Path::toNormalized(model), false); + osg::ref_ptr cnode = mSceneManager->getTemplate(model, false); if (activeGrid) { From 2ab6dd022986d7e4ed08e576b595efa5aa2fde22 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 19 Sep 2024 03:25:51 +0300 Subject: [PATCH 30/45] Address my own review concerns Correct the number of hyphens in documentation Use the correct settings tag Simplify color values --- components/widgets/tags.cpp | 10 +--------- docs/source/reference/modding/settings/GUI.rst | 10 +++++----- files/data/mygui/openmw_list.skin.xml | 12 ++++++------ files/settings-default.cfg | 9 ++++----- 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/components/widgets/tags.cpp b/components/widgets/tags.cpp index ddca0171c8..3fe72480b7 100644 --- a/components/widgets/tags.cpp +++ b/components/widgets/tags.cpp @@ -1,6 +1,5 @@ #include "tags.hpp" -#include #include #include @@ -11,8 +10,8 @@ namespace Gui bool replaceTag(std::string_view tag, MyGUI::UString& out) { std::string_view fontcolour = "fontcolour="; + std::string_view fontcolourhtml = "fontcolourhtml="; - std::string_view fontcolouroptional = "fontcoloursetting="; if (tag.starts_with(fontcolour)) { @@ -59,13 +58,6 @@ namespace Gui out = html.str(); return true; } - else if (tag.starts_with(fontcolouroptional)) - { - std::string_view colortag = tag.substr(fontcolouroptional.length()); - const MyGUI::Colour& customColour = Settings::get("GUI", colortag).get(); - out = customColour.print(); - return true; - } return false; } diff --git a/docs/source/reference/modding/settings/GUI.rst b/docs/source/reference/modding/settings/GUI.rst index 0369e23651..08ca23c7d1 100644 --- a/docs/source/reference/modding/settings/GUI.rst +++ b/docs/source/reference/modding/settings/GUI.rst @@ -158,7 +158,7 @@ The alpha value is currently ignored. 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 specific over --------------------- +------------------------- :Type: RGBA floating point :Range: 0.0 to 1.0 @@ -171,7 +171,7 @@ The alpha value is currently ignored. A dialogue topic is considered "over" if it is the active GUI element through keyboard or mouse events. color topic specific pressed --------------------- +---------------------------- :Type: RGBA floating point :Range: 0.0 to 1.0 @@ -197,7 +197,7 @@ The alpha value is currently ignored. A topic is considered "exhausted" if the response the player is about to see has already been seen. color topic exhausted over --------------------- +-------------------------- :Type: RGBA floating point :Range: 0.0 to 1.0 @@ -210,7 +210,7 @@ The alpha value is currently ignored. A dialogue topic is considered "over" if it is the active GUI element through keyboard or mouse events. color topic exhausted pressed --------------------- +----------------------------- :Type: RGBA floating point :Range: 0.0 to 1.0 @@ -220,4 +220,4 @@ This setting provides an "pressed" colour to dialogue topics that meet the color The value is composed of four floating point values representing the red, green, blue and alpha channels. The alpha value is currently ignored. -A dialogue topic is considered "pressed" if it is the active GUI element and it receives a sustained keyboard or mouse event. \ No newline at end of file +A dialogue topic is considered "pressed" if it is the active GUI element and it receives a sustained keyboard or mouse event. diff --git a/files/data/mygui/openmw_list.skin.xml b/files/data/mygui/openmw_list.skin.xml index 30ebdca35d..d17b258b80 100644 --- a/files/data/mygui/openmw_list.skin.xml +++ b/files/data/mygui/openmw_list.skin.xml @@ -129,9 +129,9 @@ - - - + + + @@ -140,9 +140,9 @@ - - - + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index d5c6e01398..df14ef0634 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -221,16 +221,15 @@ color topic enable = false # Format R G B A or empty for no special formatting # Default to blue color topic specific = 0.45 0.5 0.8 1 -color topic specific over = 0.58 0.62 0.85 1 -color topic specific pressed = 0.29 0.36 0.75 1 - +color topic specific over = 0.6 0.6 0.85 1 +color topic specific pressed = 0.3 0.35 0.75 1 # The color of dialogue topic keywords that gives already read responses # Format R G B A or empty for no special formatting # Default to grey color topic exhausted = 0.3 0.3 0.3 1 -color topic exhausted over = 0.56 0.56 0.56 1 -color topic exhausted pressed= 0.44 0.44 0.44 1 +color topic exhausted over = 0.55 0.55 0.55 1 +color topic exhausted pressed= 0.45 0.45 0.45 1 [HUD] From e369ab941e95a3884894bbd5c1eb88de9816bfac Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 19 Sep 2024 04:25:36 +0300 Subject: [PATCH 31/45] Fix word-wrapping for dialogue topics with changed skin --- apps/openmw/mwgui/dialogue.cpp | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 56d2699175..2e052de768 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -794,28 +794,31 @@ namespace MWGui if (!Settings::gui().mColorTopicEnable) return; - const std::string_view specificSkin = "MW_ListLine_Specific"; - const std::string_view exhaustedSkin = "MW_ListLine_Exhausted"; - for (const std::string& keyword : mKeywords) { int flag = MWBase::Environment::get().getDialogueManager()->getTopicFlag(ESM::RefId::stringRefId(keyword)); MyGUI::Button* button = mTopicsList->getItemWidget(keyword); - const auto holdCaption = button->getCaption(); + const auto oldCaption = button->getCaption(); + const MyGUI::IntSize oldSize = button->getSize(); + bool changed = false; if (flag & MWBase::DialogueManager::TopicType::Specific) { - button->changeWidgetSkin(specificSkin); - button->setCaption(holdCaption); - int height = button->getTextSize().height; - button->setSize(MyGUI::IntSize(button->getSize().width, height)); + button->changeWidgetSkin("MW_ListLine_Specific"); + changed = true; } else if (flag & MWBase::DialogueManager::TopicType::Exhausted) { - button->changeWidgetSkin(exhaustedSkin); - button->setCaption(holdCaption); - int height = button->getTextSize().height; - button->setSize(MyGUI::IntSize(button->getSize().width, height)); + button->changeWidgetSkin("MW_ListLine_Exhausted"); + changed = true; + } + + if (changed) + { + button->setCaption(oldCaption); + button->getSubWidgetText()->setWordWrap(true); + button->getSubWidgetText()->setTextAlign(MyGUI::Align::Left); + button->setSize(oldSize); } } } From 0b1465446d8ccc187e2716f4d71fc1732cfc326b Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 20 Sep 2024 03:42:14 +0300 Subject: [PATCH 32/45] Editor: Improve Models category layout Increase minimum input field width and give proper labels to string settings --- apps/opencs/model/prefs/state.cpp | 12 ++++++------ apps/opencs/model/prefs/stringsetting.cpp | 7 ++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 7b3b827583..a3e5a36f65 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -408,12 +408,12 @@ void CSMPrefs::State::declare() declareShortcut(mValues->mKeyBindings.mScriptEditorUncomment, "Uncomment Selection"); declareCategory("Models"); - declareString(mValues->mModels.mBaseanim, "base animations").setTooltip("3rd person base model with textkeys-data"); - declareString(mValues->mModels.mBaseanimkna, "base animations, kna") - .setTooltip("3rd person beast race base model with textkeys-data"); - declareString(mValues->mModels.mBaseanimfemale, "base animations, female") - .setTooltip("3rd person female base model with textkeys-data"); - declareString(mValues->mModels.mWolfskin, "base animations, wolf").setTooltip("3rd person werewolf skin"); + declareString(mValues->mModels.mBaseanim, "Base Animations").setTooltip("Third person base model and animations"); + declareString(mValues->mModels.mBaseanimkna, "Base Animations, Beast") + .setTooltip("Third person beast race base model and animations"); + declareString(mValues->mModels.mBaseanimfemale, "Base Animations, Female") + .setTooltip("Third person female base model and animations"); + declareString(mValues->mModels.mWolfskin, "Base Animations, Werewolf").setTooltip("Third person werewolf skin"); } void CSMPrefs::State::declareCategory(const std::string& key) diff --git a/apps/opencs/model/prefs/stringsetting.cpp b/apps/opencs/model/prefs/stringsetting.cpp index 10bd8cb558..4fa2955840 100644 --- a/apps/opencs/model/prefs/stringsetting.cpp +++ b/apps/opencs/model/prefs/stringsetting.cpp @@ -1,6 +1,7 @@ #include "stringsetting.hpp" +#include #include #include @@ -26,17 +27,21 @@ CSMPrefs::StringSetting& CSMPrefs::StringSetting::setTooltip(const std::string& CSMPrefs::SettingWidgets CSMPrefs::StringSetting::makeWidgets(QWidget* parent) { + QLabel* label = new QLabel(getLabel(), parent); + mWidget = new QLineEdit(QString::fromStdString(getValue()), parent); + mWidget->setMinimumWidth(300); if (!mTooltip.empty()) { QString tooltip = QString::fromUtf8(mTooltip.c_str()); + label->setToolTip(tooltip); mWidget->setToolTip(tooltip); } connect(mWidget, &QLineEdit::textChanged, this, &StringSetting::textChanged); - return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget }; + return SettingWidgets{ .mLabel = label, .mInput = mWidget }; } void CSMPrefs::StringSetting::updateWidget() From cd6e49796e55e5659925309462ffd9cea8360255 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 21 Sep 2024 01:39:21 +0200 Subject: [PATCH 33/45] Properly initialize local static pointers and collections Static variables should be initalized once instead of initializing them with nullptr and then doing actual initialization behind if condition. Otherwise a race condition may happen leading to undefined behaviour. --- .../mwmechanics/mechanicsmanagerimp.cpp | 23 ++-- apps/openmw/mwrender/animation.cpp | 15 ++- apps/openmw/mwworld/scene.cpp | 3 +- components/esm3/loadmgef.cpp | 78 +++++++------ components/sceneutil/attach.cpp | 42 ++++--- components/terrain/quadtreeworld.cpp | 105 +++++++++--------- 6 files changed, 144 insertions(+), 122 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index a3761e1b64..46f6440ae6 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -846,14 +846,12 @@ namespace MWMechanics mAI = true; } - bool MechanicsManager::isBoundItem(const MWWorld::Ptr& item) + namespace { - static std::set boundItemIDCache; - - // If this is empty then we haven't executed the GMST cache logic yet; or there isn't any sMagicBound* GMST's - // for some reason - if (boundItemIDCache.empty()) + std::set makeBoundItemIdCache() { + std::set boundItemIDCache; + // Build a list of known bound item ID's const MWWorld::Store& gameSettings = MWBase::Environment::get().getESMStore()->get(); @@ -870,15 +868,16 @@ namespace MWMechanics boundItemIDCache.insert(ESM::RefId::stringRefId(currentGMSTValue)); } - } - // Perform bound item check and assign the Flag_Bound bit if it passes - const ESM::RefId& tempItemID = item.getCellRef().getRefId(); + return boundItemIDCache; + } + } - if (boundItemIDCache.count(tempItemID) != 0) - return true; + bool MechanicsManager::isBoundItem(const MWWorld::Ptr& item) + { + static const std::set boundItemIdCache = makeBoundItemIdCache(); - return false; + return boundItemIdCache.find(item.getCellRef().getRefId()) != boundItemIdCache.end(); } bool MechanicsManager::isAllowedToUse(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 5b7d1db78a..c6ef6536ac 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -388,17 +388,20 @@ namespace std::string_view mEffectId; }; - osg::ref_ptr getVFXLightModelInstance() + namespace { - static osg::ref_ptr lightModel = nullptr; - - if (!lightModel) + osg::ref_ptr makeVFXLightModelInstance() { - lightModel = new osg::LightModel; + osg::ref_ptr lightModel = new osg::LightModel; lightModel->setAmbientIntensity({ 1, 1, 1, 1 }); + return lightModel; } - return lightModel; + const osg::ref_ptr& getVFXLightModelInstance() + { + static const osg::ref_ptr lightModel = makeVFXLightModelInstance(); + return lightModel; + } } void assignBoneBlendCallbackRecursive(MWRender::BoneAnimBlendController* controller, osg::Node* parent, bool isRoot) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 66aa027a89..0db44195ce 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -451,8 +451,7 @@ namespace MWWorld } else if (!ESM::isEsm4Ext(worldspace)) { - static std::vector defaultHeight; - defaultHeight.resize(verts * verts, ESM::Land::DEFAULT_HEIGHT); + static const std::vector defaultHeight(verts * verts, ESM::Land::DEFAULT_HEIGHT); mPhysics->addHeightField(defaultHeight.data(), cellX, cellY, worldsize, verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); } diff --git a/components/esm3/loadmgef.cpp b/components/esm3/loadmgef.cpp index 357dd94413..3ff725fcf7 100644 --- a/components/esm3/loadmgef.cpp +++ b/components/esm3/loadmgef.cpp @@ -132,53 +132,61 @@ namespace ESM esm.writeHNOString("DESC", mDescription); } - short MagicEffect::getResistanceEffect(short effect) + namespace { - // Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute - - // - static std::map effects; - if (effects.empty()) + std::map makeEffectsMap() { - effects[DisintegrateArmor] = Sanctuary; - effects[DisintegrateWeapon] = Sanctuary; + std::map effects; - for (int i = DrainAttribute; i <= DamageSkill; ++i) - effects[i] = ResistMagicka; - for (int i = AbsorbAttribute; i <= AbsorbSkill; ++i) - effects[i] = ResistMagicka; - for (int i = WeaknessToFire; i <= WeaknessToNormalWeapons; ++i) - effects[i] = ResistMagicka; + effects[MagicEffect::Effects::DisintegrateArmor] = MagicEffect::Effects::Sanctuary; + effects[MagicEffect::Effects::DisintegrateWeapon] = MagicEffect::Effects::Sanctuary; + + for (int i = MagicEffect::Effects::DrainAttribute; i <= MagicEffect::Effects::DamageSkill; ++i) + effects[i] = MagicEffect::Effects::ResistMagicka; + for (int i = MagicEffect::Effects::AbsorbAttribute; i <= MagicEffect::Effects::AbsorbSkill; ++i) + effects[i] = MagicEffect::Effects::ResistMagicka; + for (int i = MagicEffect::Effects::WeaknessToFire; i <= MagicEffect::Effects::WeaknessToNormalWeapons; ++i) + effects[i] = MagicEffect::Effects::ResistMagicka; - effects[Burden] = ResistMagicka; - effects[Charm] = ResistMagicka; - effects[Silence] = ResistMagicka; - effects[Blind] = ResistMagicka; - effects[Sound] = ResistMagicka; + effects[MagicEffect::Effects::Burden] = MagicEffect::Effects::ResistMagicka; + effects[MagicEffect::Effects::Charm] = MagicEffect::Effects::ResistMagicka; + effects[MagicEffect::Effects::Silence] = MagicEffect::Effects::ResistMagicka; + effects[MagicEffect::Effects::Blind] = MagicEffect::Effects::ResistMagicka; + effects[MagicEffect::Effects::Sound] = MagicEffect::Effects::ResistMagicka; for (int i = 0; i < 2; ++i) { - effects[CalmHumanoid + i] = ResistMagicka; - effects[FrenzyHumanoid + i] = ResistMagicka; - effects[DemoralizeHumanoid + i] = ResistMagicka; - effects[RallyHumanoid + i] = ResistMagicka; + effects[MagicEffect::Effects::CalmHumanoid + i] = MagicEffect::Effects::ResistMagicka; + effects[MagicEffect::Effects::FrenzyHumanoid + i] = MagicEffect::Effects::ResistMagicka; + effects[MagicEffect::Effects::DemoralizeHumanoid + i] = MagicEffect::Effects::ResistMagicka; + effects[MagicEffect::Effects::RallyHumanoid + i] = MagicEffect::Effects::ResistMagicka; } - effects[TurnUndead] = ResistMagicka; + effects[MagicEffect::Effects::TurnUndead] = MagicEffect::Effects::ResistMagicka; + + effects[MagicEffect::Effects::FireDamage] = MagicEffect::Effects::ResistFire; + effects[MagicEffect::Effects::FrostDamage] = MagicEffect::Effects::ResistFrost; + effects[MagicEffect::Effects::ShockDamage] = MagicEffect::Effects::ResistShock; + effects[MagicEffect::Effects::Vampirism] = MagicEffect::Effects::ResistCommonDisease; + effects[MagicEffect::Effects::Corprus] = MagicEffect::Effects::ResistCorprusDisease; + effects[MagicEffect::Effects::Poison] = MagicEffect::Effects::ResistPoison; + effects[MagicEffect::Effects::Paralyze] = MagicEffect::Effects::ResistParalysis; - effects[FireDamage] = ResistFire; - effects[FrostDamage] = ResistFrost; - effects[ShockDamage] = ResistShock; - effects[Vampirism] = ResistCommonDisease; - effects[Corprus] = ResistCorprusDisease; - effects[Poison] = ResistPoison; - effects[Paralyze] = ResistParalysis; + return effects; } + } - if (effects.find(effect) != effects.end()) - return effects[effect]; - else - return -1; + short MagicEffect::getResistanceEffect(short effect) + { + // Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute + + // + static const std::map effects = makeEffectsMap(); + + if (const auto it = effects.find(effect); it != effects.end()) + return it->second; + + return -1; } short MagicEffect::getWeaknessEffect(short effect) diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index 1c21221ac4..7b43380c98 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -82,18 +82,32 @@ namespace SceneUtil std::string_view mFilter; }; - void mergeUserData(const osg::UserDataContainer* source, osg::Object* target) + namespace { - if (!source) - return; - if (!target->getUserDataContainer()) - target->setUserDataContainer(osg::clone(source, osg::CopyOp::SHALLOW_COPY)); - else + void mergeUserData(const osg::UserDataContainer* source, osg::Object* target) { - for (unsigned int i = 0; i < source->getNumUserObjects(); ++i) - target->getUserDataContainer()->addUserObject( - osg::clone(source->getUserObject(i), osg::CopyOp::SHALLOW_COPY)); + if (!source) + return; + + if (!target->getUserDataContainer()) + target->setUserDataContainer(osg::clone(source, osg::CopyOp::SHALLOW_COPY)); + else + { + for (unsigned int i = 0; i < source->getNumUserObjects(); ++i) + target->getUserDataContainer()->addUserObject( + osg::clone(source->getUserObject(i), osg::CopyOp::SHALLOW_COPY)); + } + } + + osg::ref_ptr makeFrontFaceStateSet() + { + osg::ref_ptr frontFace = new osg::FrontFace; + frontFace->setMode(osg::FrontFace::CLOCKWISE); + + osg::ref_ptr frontFaceStateSet = new osg::StateSet; + frontFaceStateSet->setAttributeAndModes(frontFace, osg::StateAttribute::ON); + return frontFaceStateSet; } } @@ -159,14 +173,8 @@ namespace SceneUtil // Note: for absolute correctness we would need to check the current front face for every mesh then // invert it However MW isn't doing this either, so don't. Assuming all meshes are using backface // culling is more efficient. - static osg::ref_ptr frontFaceStateSet; - if (!frontFaceStateSet) - { - frontFaceStateSet = new osg::StateSet; - osg::FrontFace* frontFace = new osg::FrontFace; - frontFace->setMode(osg::FrontFace::CLOCKWISE); - frontFaceStateSet->setAttributeAndModes(frontFace, osg::StateAttribute::ON); - } + static const osg::ref_ptr frontFaceStateSet = makeFrontFaceStateSet(); + trans->setStateSet(frontFaceStateSet); } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 63b55abb21..99938d7b3a 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -404,63 +404,68 @@ namespace Terrain } } - void updateWaterCullingView( - HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld) + namespace { - if (!(cv->getTraversalMask() & callback->getCullMask())) - return; - float lowZ = std::numeric_limits::max(); - float highZ = callback->getHighZ(); - if (cv->getEyePoint().z() <= highZ || outofworld) + osg::ref_ptr makeStateSet() { - callback->setLowZ(-std::numeric_limits::max()); - return; + osg::ref_ptr stateSet = new osg::StateSet; + stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + stateSet->setAttributeAndModes( + new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), + osg::StateAttribute::ON); + osg::ref_ptr material = new osg::Material; + material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 1, 1)); + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1)); + material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1)); + stateSet->setAttributeAndModes(material, osg::StateAttribute::ON); + stateSet->setRenderBinDetails(100, "RenderBin"); + return stateSet; } - cv->pushCurrentMask(); - static bool debug = getenv("OPENMW_WATER_CULLING_DEBUG") != nullptr; - for (unsigned int i = 0; i < vd->getNumEntries(); ++i) + + void updateWaterCullingView( + HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld) { - ViewDataEntry& entry = vd->getEntry(i); - osg::BoundingBox bb - = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox(); - if (!bb.valid()) - continue; - osg::Vec3f ofs( - entry.mNode->getCenter().x() * cellworldsize, entry.mNode->getCenter().y() * cellworldsize, 0.f); - bb._min += ofs; - bb._max += ofs; - bb._min.z() = highZ; - bb._max.z() = highZ; - if (cv->isCulled(bb)) - continue; - lowZ = bb._min.z(); - - if (!debug) - break; - osg::Box* b = new osg::Box; - b->set(bb.center(), bb._max - bb.center()); - osg::ShapeDrawable* drw = new osg::ShapeDrawable(b); - static osg::ref_ptr stateset = nullptr; - if (!stateset) + if (!(cv->getTraversalMask() & callback->getCullMask())) + return; + float lowZ = std::numeric_limits::max(); + float highZ = callback->getHighZ(); + if (cv->getEyePoint().z() <= highZ || outofworld) { - stateset = new osg::StateSet; - stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - stateset->setAttributeAndModes( - new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), - osg::StateAttribute::ON); - osg::Material* m = new osg::Material; - m->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 1, 1)); - m->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1)); - m->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1)); - stateset->setAttributeAndModes(m, osg::StateAttribute::ON); - stateset->setRenderBinDetails(100, "RenderBin"); + callback->setLowZ(-std::numeric_limits::max()); + return; + } + cv->pushCurrentMask(); + static bool debug = getenv("OPENMW_WATER_CULLING_DEBUG") != nullptr; + for (unsigned int i = 0; i < vd->getNumEntries(); ++i) + { + ViewDataEntry& entry = vd->getEntry(i); + osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0)) + ->getWaterBoundingBox(); + if (!bb.valid()) + continue; + osg::Vec3f ofs( + entry.mNode->getCenter().x() * cellworldsize, entry.mNode->getCenter().y() * cellworldsize, 0.f); + bb._min += ofs; + bb._max += ofs; + bb._min.z() = highZ; + bb._max.z() = highZ; + if (cv->isCulled(bb)) + continue; + lowZ = bb._min.z(); + + if (!debug) + break; + osg::Box* b = new osg::Box; + b->set(bb.center(), bb._max - bb.center()); + osg::ShapeDrawable* drw = new osg::ShapeDrawable(b); + static const osg::ref_ptr stateset = makeStateSet(); + drw->setStateSet(stateset); + drw->accept(*cv); } - drw->setStateSet(stateset); - drw->accept(*cv); + callback->setLowZ(lowZ); + cv->popCurrentMask(); } - callback->setLowZ(lowZ); - cv->popCurrentMask(); } void QuadTreeWorld::accept(osg::NodeVisitor& nv) From 2546ce2b162e7194db2926430a5e71bdf6737a02 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 22 Sep 2024 18:24:21 +0200 Subject: [PATCH 34/45] Don't allow items that are better than new to be repaired --- CHANGELOG.md | 1 + apps/openmw/mwgui/merchantrepair.cpp | 2 +- apps/openmw/mwgui/sortfilteritemmodel.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aef21779d0..bcae64b27a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -191,6 +191,7 @@ Bug #8097: GetEffect doesn't detect 0 magnitude spells Bug #8124: Normal weapon resistance is applied twice for NPCs Bug #8132: Actors without hello responses turn to face the player + Bug #8171: Items with more than 100% health can be repaired Feature #1415: Infinite fall failsafe Feature #2566: Handle NAM9 records for manual cell references Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 3be0bb1c06..a59f225e9e 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -55,7 +55,7 @@ namespace MWGui { int maxDurability = iter->getClass().getItemMaxHealth(*iter); int durability = iter->getClass().getItemHealth(*iter); - if (maxDurability == durability || maxDurability == 0) + if (maxDurability <= durability || maxDurability == 0) continue; int basePrice = iter->getClass().getValue(*iter); diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index e5b87abff7..fe85ea4bd0 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -288,7 +288,7 @@ namespace MWGui if ((mFilter & Filter_OnlyRepairable) && (!base.getClass().hasItemHealth(base) - || (base.getClass().getItemHealth(base) == base.getClass().getItemMaxHealth(base)) + || (base.getClass().getItemHealth(base) >= base.getClass().getItemMaxHealth(base)) || (base.getType() != ESM::Weapon::sRecordId && base.getType() != ESM::Armor::sRecordId))) return false; From b412b60a07224512df9377466c7cdb9020a122fb Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 22 Sep 2024 21:10:59 +0300 Subject: [PATCH 35/45] Update topic highlighting documentation --- .../source/reference/modding/settings/GUI.rst | 26 +++++++++---------- files/settings-default.cfg | 6 +---- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/docs/source/reference/modding/settings/GUI.rst b/docs/source/reference/modding/settings/GUI.rst index 08ca23c7d1..118fd19212 100644 --- a/docs/source/reference/modding/settings/GUI.rst +++ b/docs/source/reference/modding/settings/GUI.rst @@ -148,8 +148,8 @@ color topic specific -------------------- :Type: RGBA floating point -:Range: 0.0 to 1.0 -:Default: empty +:Range: 0.0 to 1.0 for each channel +:Default: 0.45 0.5 0.8 1 (blue) 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. @@ -161,8 +161,8 @@ color topic specific over ------------------------- :Type: RGBA floating point -:Range: 0.0 to 1.0 -:Default: empty +:Range: 0.0 to 1.0 for each channel +:Default: 0.6 0.6 0.85 1 (blue) This setting provides an "over" colour to dialogue topics that meet the color topic specific criteria. The value is composed of four floating point values representing the red, green, blue and alpha channels. @@ -174,8 +174,8 @@ color topic specific pressed ---------------------------- :Type: RGBA floating point -:Range: 0.0 to 1.0 -:Default: empty +:Range: 0.0 to 1.0 for each channel +:Default: 0.3 0.35 0.75 1 (blue) This setting provides an "pressed" colour to dialogue topics that meet the color topic specific criteria. The value is composed of four floating point values representing the red, green, blue and alpha channels. @@ -187,8 +187,8 @@ color topic exhausted --------------------- :Type: RGBA floating point -:Range: 0.0 to 1.0 -:Default: empty +:Range: 0.0 to 1.0 for each channel +:Default: 0.3 0.3 0.3 1 (grey) 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. @@ -200,8 +200,8 @@ color topic exhausted over -------------------------- :Type: RGBA floating point -:Range: 0.0 to 1.0 -:Default: empty +:Range: 0.0 to 1.0 for each channel +:Default: 0.55 0.55 0.55 1 (grey) This setting provides an "over" colour to dialogue topics that meet the color topic exhausted criteria. The value is composed of four floating point values representing the red, green, blue and alpha channels. @@ -213,10 +213,10 @@ color topic exhausted pressed ----------------------------- :Type: RGBA floating point -:Range: 0.0 to 1.0 -:Default: empty +:Range: 0.0 to 1.0 for each channel +:Default: 0.45 0.45 0.45 1 (grey) -This setting provides an "pressed" colour to dialogue topics that meet the color topic exhausted criteria. +This setting provides a "pressed" colour to dialogue topics that meet the color topic exhausted criteria. The value is composed of four floating point values representing the red, green, blue and alpha channels. The alpha value is currently ignored. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index df14ef0634..1fa9db2640 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -218,18 +218,14 @@ keyboard navigation = true color topic enable = false # The color of dialogue topic keywords that gives unique actor responses -# Format R G B A or empty for no special formatting -# Default to blue color topic specific = 0.45 0.5 0.8 1 color topic specific over = 0.6 0.6 0.85 1 color topic specific pressed = 0.3 0.35 0.75 1 # The color of dialogue topic keywords that gives already read responses -# Format R G B A or empty for no special formatting -# Default to grey color topic exhausted = 0.3 0.3 0.3 1 color topic exhausted over = 0.55 0.55 0.55 1 -color topic exhausted pressed= 0.45 0.45 0.45 1 +color topic exhausted pressed = 0.45 0.45 0.45 1 [HUD] From 57add0f3f6739235b841ab3b71aca38428cb3470 Mon Sep 17 00:00:00 2001 From: Sarah Sunday <1644563-ssunday@users.noreply.gitlab.com> Date: Mon, 23 Sep 2024 10:11:04 +0000 Subject: [PATCH 36/45] CI - Use XZ for Mac builds, clean some steps --- .github/workflows/openmw.yml | 4 +--- .gitlab-ci.yml | 2 -- CI/before_install.osx.sh | 12 ++++-------- CI/before_script.osx.sh | 1 + 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/.github/workflows/openmw.yml b/.github/workflows/openmw.yml index 9114ae7711..b0e05ea894 100644 --- a/.github/workflows/openmw.yml +++ b/.github/workflows/openmw.yml @@ -83,9 +83,7 @@ jobs: max-size: 1000M - name: Configure - run: | - rm -fr build # remove the build directory - CI/before_script.osx.sh + run: CI/before_script.osx.sh - name: Build run: | cd build diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fdc9c19c07..683bdd06c7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -501,7 +501,6 @@ Ubuntu_GCC_integration_tests_asan: paths: - ccache/ script: - - rm -fr build # remove the build directory - CI/before_install.osx.sh - export CCACHE_BASEDIR="$(pwd)" - export CCACHE_DIR="$(pwd)/ccache" @@ -521,7 +520,6 @@ Ubuntu_GCC_integration_tests_asan: artifacts: paths: - build/OpenMW-*.dmg - - "build/**/*.log" macOS14_Xcode15_arm64: extends: .MacOS diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 660ecf4adc..0120c55202 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -4,14 +4,9 @@ export HOMEBREW_NO_EMOJI=1 export HOMEBREW_NO_INSTALL_CLEANUP=1 export HOMEBREW_AUTOREMOVE=1 -# workaround for gitlab's pre-installed brew -# purge large and unnecessary packages that get in our way and have caused issues -brew uninstall ruby php openjdk node postgresql maven curl || true - brew tap --repair brew update --quiet -# Some of these tools can come from places other than brew, so check before installing brew install curl xquartz gd fontconfig freetype harfbuzz brotli command -v ccache >/dev/null 2>&1 || brew install ccache @@ -27,8 +22,9 @@ cmake --version qmake --version if [[ "${MACOS_AMD64}" ]]; then - curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20221113.zip -o ~/openmw-deps.zip + curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20240802.zip -o ~/openmw-deps.zip + unzip -o ~/openmw-deps.zip -d /tmp > /dev/null else - curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20240802_arm64.zip -o ~/openmw-deps.zip + curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20240818-arm64.tar.xz -o ~/openmw-deps.tar.xz + tar xf ~/openmw-deps.tar.xz -C /tmp > /dev/null fi -unzip -o ~/openmw-deps.zip -d /tmp > /dev/null diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index d3e3698ab2..9f7a5bde8f 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -3,6 +3,7 @@ # Silence a git warning git config --global advice.detachedHead false +rm -fr build mkdir build cd build From 7f1ce5e812b8ca8f69537fe981c3f5d0f7a4b759 Mon Sep 17 00:00:00 2001 From: Arnaud Dochain Date: Tue, 24 Sep 2024 17:05:51 +0000 Subject: [PATCH 37/45] Add OpenMW Music French setting and unfoformize the other settings tabs --- files/data/l10n/OMWCamera/fr.yaml | 4 ++-- files/data/l10n/OMWControls/fr.yaml | 12 ++++++------ files/data/l10n/OMWMusic/fr.yaml | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/files/data/l10n/OMWCamera/fr.yaml b/files/data/l10n/OMWCamera/fr.yaml index a9dde33f79..00a0e3d3c1 100644 --- a/files/data/l10n/OMWCamera/fr.yaml +++ b/files/data/l10n/OMWCamera/fr.yaml @@ -1,5 +1,5 @@ -Camera: "Caméra d’OpenMW" -settingsPageDescription: "Configuration de la caméra d’OpenMW" +Camera: "OpenMW : Caméra" +settingsPageDescription: "Paramètres de la caméra d’OpenMW" thirdPersonSettings: "Vue à la troisième personne" diff --git a/files/data/l10n/OMWControls/fr.yaml b/files/data/l10n/OMWControls/fr.yaml index 95fa88a6ac..7dc18dc00f 100644 --- a/files/data/l10n/OMWControls/fr.yaml +++ b/files/data/l10n/OMWControls/fr.yaml @@ -1,19 +1,19 @@ ControlsPage: "OpenMW : Contrôles" -ControlsPageDescription: "Paramètres additionnels de contrôle" +ControlsPageDescription: "Paramètres additionnels des contrôles d'OpenMW" MovementSettings: "Mouvements" alwaysRun: "Course permanente" alwaysRunDescription: | - Actif : Le personnage se déplace par défaut en courant ; \n\n - Inactif : Le personnage se déplace par défaut en marchant.\n\n - La touche Maj. inverse temporairement ce paramètre.\n\n + Actif : Le personnage se déplace par défaut en courant ; + Inactif : Le personnage se déplace par défaut en marchant. + La touche Maj. inverse temporairement ce paramètre. La touche Verr Maj inverse ce paramètre lorsqu'elle est verrouillée. toggleSneak: "Mode discrétion maintenu" toggleSneakDescription: | - Une simple pression de la touche associée (Ctrl par défaut) active le mode discrétion, une seconde pression désactive le mode discrétion.\n\n - Il n'est plus nécessaire de maintenir une touche appuyée pour que le mode discrétion soit actif.\n\n + Une simple pression de la touche associée (Ctrl par défaut) active le mode discrétion, une seconde pression désactive le mode discrétion. + Il n'est plus nécessaire de maintenir une touche appuyée pour que le mode discrétion soit actif. Certains joueurs ayant une utilisation intensive du mode discrétion considèrent qu'il est plus aisé de contrôler leur personnage ainsi. smoothControllerMovement: "Mouvements à la manette adoucis" diff --git a/files/data/l10n/OMWMusic/fr.yaml b/files/data/l10n/OMWMusic/fr.yaml index 0e15d0a411..0f2977dbf0 100755 --- a/files/data/l10n/OMWMusic/fr.yaml +++ b/files/data/l10n/OMWMusic/fr.yaml @@ -1,7 +1,7 @@ -#Music: "OpenMW Music" -#settingsPageDescription: "OpenMW Music settings" +Music: "OpenMW : Musique" +settingsPageDescription: "Paramètre de la musique d'OpenMW" -#musicSettings: "Music configuration" +musicSettings: "Configuration de la musique" -#CombatMusicEnabled: "Play combat music" -#CombatMusicEnabledDescription: "Whether to switch to combat music when there are actors in combat." +CombatMusicEnabled: "Jouer la musique de combat" +CombatMusicEnabledDescription: "Si activé, le jeu bascule vers la musique de combat dès qu'un personnage est en combat." From e7a31373c9f854c4c9fb4ad54610793c33a184d9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 25 Sep 2024 01:50:31 +0100 Subject: [PATCH 38/45] Partially fix inventory doll when non-compute-shader ripples are used with FFP --- apps/openmw/mwrender/ripples.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index d9c1b41737..08a96d4b1b 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -206,7 +206,7 @@ namespace MWRender }; // PASS: Blot in all ripple spawners - mProgramBlobber->apply(state); + state.applyAttribute(mProgramBlobber); state.apply(frameState.mStateset); if (mUseCompute) @@ -225,7 +225,7 @@ namespace MWRender } // PASS: Wave simulation - mProgramSimulation->apply(state); + state.applyAttribute(mProgramSimulation); state.apply(frameState.mStateset); if (mUseCompute) From 95a1183ad36c8547f904c65eedee933192c8fcfa Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 25 Sep 2024 16:10:18 +0100 Subject: [PATCH 39/45] Fix OpenGL debug groups --- components/debug/gldebug.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/debug/gldebug.cpp b/components/debug/gldebug.cpp index 29036af162..482755b761 100644 --- a/components/debug/gldebug.cpp +++ b/components/debug/gldebug.cpp @@ -239,7 +239,7 @@ namespace Debug group->push(state); lastAppliedStack.push_back(group); } - if (!(lastAppliedStack.back() == this)) + if (lastAppliedStack.empty() || !(lastAppliedStack.back() == this)) { push(state); lastAppliedStack.push_back(this); From bd8386459a843bd7260cf382305a601a1e06240d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 25 Sep 2024 16:14:19 +0100 Subject: [PATCH 40/45] Fix inventory doll when ripple fragment shader path is used Basically don't abuse OSG as badly. We need to let it know we've bound a shader program so it doesn't assume the FFP is still used, but it doesn't have a built-in way to apply the uniforms when doing so, so we need to do it manually. --- apps/openmw/mwrender/ripples.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index 08a96d4b1b..bb8248217a 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -206,8 +206,14 @@ namespace MWRender }; // PASS: Blot in all ripple spawners + state.pushStateSet(frameState.mStateset); + state.apply(); state.applyAttribute(mProgramBlobber); - state.apply(frameState.mStateset); + for (const auto& [name, stack] : state.getUniformMap()) + { + if (!stack.uniformVec.empty()) + state.getLastAppliedProgramObject()->apply(*(stack.uniformVec.back().first)); + } if (mUseCompute) { @@ -226,7 +232,11 @@ namespace MWRender // PASS: Wave simulation state.applyAttribute(mProgramSimulation); - state.apply(frameState.mStateset); + for (const auto& [name, stack] : state.getUniformMap()) + { + if (!stack.uniformVec.empty()) + state.getLastAppliedProgramObject()->apply(*(stack.uniformVec.back().first)); + } if (mUseCompute) { @@ -242,6 +252,8 @@ namespace MWRender state.applyTextureAttribute(0, mTextures[1]); osg::Geometry::drawImplementation(renderInfo); } + + state.popStateSet(); } osg::Texture* RipplesSurface::getColorTexture() const From 1d98b5c66b63661ad2e481d304a822ece6e2d04e Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 21 Sep 2024 00:30:01 +0200 Subject: [PATCH 41/45] Remove redundant toNormalized --- apps/openmw/mwrender/actoranimation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 96141c9ebf..b4983087b5 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -385,7 +385,7 @@ namespace MWRender if (!weaponNode->getNumChildren()) { osg::ref_ptr fallbackNode - = mResourceSystem->getSceneManager()->getInstance(VFS::Path::toNormalized(mesh), weaponNode); + = mResourceSystem->getSceneManager()->getInstance(mesh, weaponNode); resetControllers(fallbackNode); } From 7a5c478e34ee6e8b66dcafc5f6203093a1a4a77c Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 23 Sep 2024 23:53:38 +0200 Subject: [PATCH 42/45] Use normalized path in PreloadItem --- apps/openmw/mwworld/cellpreloader.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 05d6efc494..e54e9d84df 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -105,8 +106,8 @@ namespace MWWorld } } - std::string mesh; - std::string kfname; + VFS::Path::Normalized mesh; + VFS::Path::Normalized kfname; for (std::string_view path : mMeshes) { if (mAbort) @@ -121,19 +122,15 @@ namespace MWWorld if (!vfs.exists(mesh)) continue; - size_t slashpos = mesh.find_last_of("/\\"); - if (slashpos != std::string::npos && slashpos != mesh.size() - 1) + if (Misc::getFileName(mesh).starts_with('x') && Misc::getFileExtension(mesh) == "nif") { - if (Misc::StringUtils::toLower(mesh[slashpos + 1]) == 'x' - && Misc::StringUtils::ciEndsWith(mesh, ".nif")) - { - kfname = mesh; - kfname.replace(kfname.size() - 4, 4, ".kf"); - if (vfs.exists(kfname)) - mPreloadedObjects.insert(mKeyframeManager->get(kfname)); - } + kfname = mesh; + kfname.changeExtension("kf"); + if (vfs.exists(kfname)) + mPreloadedObjects.insert(mKeyframeManager->get(kfname)); } - mPreloadedObjects.insert(mSceneManager->getTemplate(VFS::Path::toNormalized(mesh))); + + mPreloadedObjects.insert(mSceneManager->getTemplate(mesh)); if (mPreloadInstances) mPreloadedObjects.insert(mBulletShapeManager->cacheInstance(mesh)); else From 63e984ba24bc718cffe503cdf328ab0598eb6ec5 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 24 Sep 2024 00:14:18 +0200 Subject: [PATCH 43/45] Use normalized path in BulletShapeManager --- .../detournavigator/asyncnavmeshupdater.cpp | 3 +- apps/navmeshtool/worldspacedata.cpp | 3 +- apps/openmw/mwphysics/physicssystem.cpp | 14 ++- components/nifbullet/bulletnifloader.cpp | 4 +- components/resource/bulletshape.hpp | 5 +- components/resource/bulletshapemanager.cpp | 114 ++++++++---------- components/resource/bulletshapemanager.hpp | 13 +- components/resource/foreachbulletobject.cpp | 9 +- components/resource/multiobjectcache.cpp | 23 ++-- components/resource/multiobjectcache.hpp | 12 +- 10 files changed, 92 insertions(+), 108 deletions(-) diff --git a/apps/components_tests/detournavigator/asyncnavmeshupdater.cpp b/apps/components_tests/detournavigator/asyncnavmeshupdater.cpp index 14c2a3bfe4..ea9efc3df2 100644 --- a/apps/components_tests/detournavigator/asyncnavmeshupdater.cpp +++ b/apps/components_tests/detournavigator/asyncnavmeshupdater.cpp @@ -33,7 +33,8 @@ namespace { const ObjectId id(&shape); osg::ref_ptr bulletShape(new Resource::BulletShape); - bulletShape->mFileName = "test.nif"; + constexpr VFS::Path::NormalizedView test("test.nif"); + bulletShape->mFileName = test; bulletShape->mFileHash = "test_hash"; ObjectTransform objectTransform; std::fill(std::begin(objectTransform.mPosition.pos), std::end(objectTransform.mPosition.pos), 0.1f); diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index 3a4ff63329..077b43f72f 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -131,7 +131,8 @@ namespace NavMeshTool osg::ref_ptr shape = [&] { try { - return bulletShapeManager.getShape(Misc::ResourceHelpers::correctMeshPath(model)); + return bulletShapeManager.getShape( + VFS::Path::toNormalized(Misc::ResourceHelpers::correctMeshPath(model))); } catch (const std::exception& e) { diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 20a9c38b0f..5601b486d0 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -412,9 +412,9 @@ namespace MWPhysics if (ptr.mRef->mData.mPhysicsPostponed) return; - std::string animationMesh = mesh; - if (ptr.getClass().useAnim()) - animationMesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS()); + const VFS::Path::Normalized animationMesh = ptr.getClass().useAnim() + ? Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS()) + : mesh; osg::ref_ptr shapeInstance = mShapeManager->getInstance(animationMesh); if (!shapeInstance || !shapeInstance->mCollisionShape) return; @@ -562,7 +562,8 @@ namespace MWPhysics void PhysicsSystem::addActor(const MWWorld::Ptr& ptr, const std::string& mesh) { - std::string animationMesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS()); + const VFS::Path::Normalized animationMesh + = Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS()); osg::ref_ptr shape = mShapeManager->getShape(animationMesh); // Try to get shape from basic model as fallback for creatures @@ -570,7 +571,7 @@ namespace MWPhysics { if (animationMesh != mesh) { - shape = mShapeManager->getShape(mesh); + shape = mShapeManager->getShape(VFS::Path::toNormalized(mesh)); } } @@ -590,7 +591,8 @@ namespace MWPhysics int PhysicsSystem::addProjectile( const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius) { - osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); + osg::ref_ptr shapeInstance + = mShapeManager->getInstance(VFS::Path::toNormalized(mesh)); assert(shapeInstance); float radius = computeRadius ? shapeInstance->mCollisionBox.mExtents.length() / 2.f : 1.f; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index bcda7efe70..6bf6ad89bb 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -53,7 +53,7 @@ namespace NifBullet mShape->mFileName = nif.getFilename(); if (roots.empty()) { - warn("Found no root nodes in NIF file " + mShape->mFileName); + warn("Found no root nodes in NIF file " + mShape->mFileName.value()); return mShape; } @@ -93,7 +93,7 @@ namespace NifBullet } else { - warn("Invalid Bounding Box node bounds in file " + mShape->mFileName); + warn("Invalid Bounding Box node bounds in file " + mShape->mFileName.value()); } return true; } diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index 0a1b98bf7c..d9aad0f092 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -1,7 +1,6 @@ #ifndef OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H #define OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H -#include #include #include @@ -12,6 +11,8 @@ #include #include +#include + class btCollisionShape; namespace NifBullet @@ -56,7 +57,7 @@ namespace Resource // we store the node's record index mapped to the child index of the shape in the btCompoundShape. std::map mAnimatedShapes; - std::string mFileName; + VFS::Path::Normalized mFileName; std::string mFileHash; VisualCollisionType mVisualCollisionType = VisualCollisionType::None; diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 93c53d8cb0..345efc7fbd 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -106,93 +106,83 @@ namespace Resource { } - BulletShapeManager::~BulletShapeManager() {} + BulletShapeManager::~BulletShapeManager() = default; - osg::ref_ptr BulletShapeManager::getShape(const std::string& name) + osg::ref_ptr BulletShapeManager::getShape(VFS::Path::NormalizedView name) { - const VFS::Path::Normalized normalized(name); + if (osg::ref_ptr obj = mCache->getRefFromObjectCache(name)) + return osg::ref_ptr(static_cast(obj.get())); osg::ref_ptr shape; - osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); - if (obj) - shape = osg::ref_ptr(static_cast(obj.get())); + + if (Misc::getFileExtension(name.value()) == "nif") + { + NifBullet::BulletNifLoader loader; + shape = loader.load(*mNifFileManager->get(name)); + } else { - if (Misc::getFileExtension(normalized) == "nif") + // TODO: support .bullet shape files + + osg::ref_ptr constNode(mSceneManager->getTemplate(name)); + // const-trickery required because there is no const version of NodeVisitor + osg::ref_ptr node(const_cast(constNode.get())); + + // Check first if there's a custom collision node + unsigned int visitAllNodesMask = 0xffffffff; + SceneUtil::FindByNameVisitor nameFinder("Collision"); + nameFinder.setTraversalMask(visitAllNodesMask); + nameFinder.setNodeMaskOverride(visitAllNodesMask); + node->accept(nameFinder); + if (nameFinder.mFoundNode) { - NifBullet::BulletNifLoader loader; - shape = loader.load(*mNifFileManager->get(normalized)); + NodeToShapeVisitor visitor; + visitor.setTraversalMask(visitAllNodesMask); + visitor.setNodeMaskOverride(visitAllNodesMask); + nameFinder.mFoundNode->accept(visitor); + shape = visitor.getShape(); } - else + + // Generate a collision shape from the mesh + if (!shape) { - // TODO: support .bullet shape files - - osg::ref_ptr constNode(mSceneManager->getTemplate(normalized)); - osg::ref_ptr node(const_cast( - constNode.get())); // const-trickery required because there is no const version of NodeVisitor - - // Check first if there's a custom collision node - unsigned int visitAllNodesMask = 0xffffffff; - SceneUtil::FindByNameVisitor nameFinder("Collision"); - nameFinder.setTraversalMask(visitAllNodesMask); - nameFinder.setNodeMaskOverride(visitAllNodesMask); - node->accept(nameFinder); - if (nameFinder.mFoundNode) - { - NodeToShapeVisitor visitor; - visitor.setTraversalMask(visitAllNodesMask); - visitor.setNodeMaskOverride(visitAllNodesMask); - nameFinder.mFoundNode->accept(visitor); - shape = visitor.getShape(); - } - - // Generate a collision shape from the mesh + NodeToShapeVisitor visitor; + node->accept(visitor); + shape = visitor.getShape(); if (!shape) - { - NodeToShapeVisitor visitor; - node->accept(visitor); - shape = visitor.getShape(); - if (!shape) - return osg::ref_ptr(); - } - - if (shape != nullptr) - { - shape->mFileName = normalized; - constNode->getUserValue(Misc::OsgUserValues::sFileHash, shape->mFileHash); - } + return osg::ref_ptr(); } - mCache->addEntryToObjectCache(normalized, shape); + if (shape != nullptr) + { + shape->mFileName = name; + constNode->getUserValue(Misc::OsgUserValues::sFileHash, shape->mFileHash); + } } + + mCache->addEntryToObjectCache(name.value(), shape); + return shape; } - osg::ref_ptr BulletShapeManager::cacheInstance(const std::string& name) + osg::ref_ptr BulletShapeManager::cacheInstance(VFS::Path::NormalizedView name) { - const std::string normalized = VFS::Path::normalizeFilename(name); - - osg::ref_ptr instance = createInstance(normalized); - if (instance) - mInstanceCache->addEntryToObjectCache(normalized, instance.get()); + osg::ref_ptr instance = createInstance(name); + if (instance != nullptr) + mInstanceCache->addEntryToObjectCache(name, instance.get()); return instance; } - osg::ref_ptr BulletShapeManager::getInstance(const std::string& name) + osg::ref_ptr BulletShapeManager::getInstance(VFS::Path::NormalizedView name) { - const std::string normalized = VFS::Path::normalizeFilename(name); - - osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); - if (obj.get()) + if (osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(name)) return static_cast(obj.get()); - else - return createInstance(normalized); + return createInstance(name); } - osg::ref_ptr BulletShapeManager::createInstance(const std::string& name) + osg::ref_ptr BulletShapeManager::createInstance(VFS::Path::NormalizedView name) { - osg::ref_ptr shape = getShape(name); - if (shape) + if (osg::ref_ptr shape = getShape(name)) return makeInstance(std::move(shape)); return osg::ref_ptr(); } diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index a81296fdfd..3c5a495c77 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -1,11 +1,10 @@ #ifndef OPENMW_COMPONENTS_BULLETSHAPEMANAGER_H #define OPENMW_COMPONENTS_BULLETSHAPEMANAGER_H -#include -#include - #include +#include + #include "bulletshape.hpp" #include "resourcemanager.hpp" @@ -30,16 +29,16 @@ namespace Resource ~BulletShapeManager(); /// @note May return a null pointer if the object has no shape. - osg::ref_ptr getShape(const std::string& name); + osg::ref_ptr getShape(VFS::Path::NormalizedView name); /// Create an instance of the given shape and cache it for later use, so that future calls to getInstance() can /// simply return the cached instance instead of having to create a new one. /// @note The returned ref_ptr may be kept by the caller to ensure that the instance stays in cache for as long /// as needed. - osg::ref_ptr cacheInstance(const std::string& name); + osg::ref_ptr cacheInstance(VFS::Path::NormalizedView name); /// @note May return a null pointer if the object has no shape. - osg::ref_ptr getInstance(const std::string& name); + osg::ref_ptr getInstance(VFS::Path::NormalizedView name); /// @see ResourceManager::updateCache void updateCache(double referenceTime) override; @@ -49,7 +48,7 @@ namespace Resource void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; private: - osg::ref_ptr createInstance(const std::string& name); + osg::ref_ptr createInstance(VFS::Path::NormalizedView name); osg::ref_ptr mInstanceCache; SceneManager* mSceneManager; diff --git a/components/resource/foreachbulletobject.cpp b/components/resource/foreachbulletobject.cpp index b936e39a2a..9c205c8228 100644 --- a/components/resource/foreachbulletobject.cpp +++ b/components/resource/foreachbulletobject.cpp @@ -16,10 +16,6 @@ #include #include -#include -#include -#include -#include #include #include @@ -97,7 +93,7 @@ namespace Resource for (CellRef& cellRef : cellRefs) { - std::string model(getModel(esmData, cellRef.mRefId, cellRef.mType)); + VFS::Path::Normalized model(getModel(esmData, cellRef.mRefId, cellRef.mType)); if (model.empty()) continue; @@ -107,7 +103,8 @@ namespace Resource osg::ref_ptr shape = [&] { try { - return bulletShapeManager.getShape("meshes/" + model); + constexpr VFS::Path::NormalizedView prefix("meshes"); + return bulletShapeManager.getShape(prefix / model); } catch (const std::exception& e) { diff --git a/components/resource/multiobjectcache.cpp b/components/resource/multiobjectcache.cpp index 71500b0ceb..0be9be232e 100644 --- a/components/resource/multiobjectcache.cpp +++ b/components/resource/multiobjectcache.cpp @@ -6,11 +6,6 @@ namespace Resource { - - MultiObjectCache::MultiObjectCache() {} - - MultiObjectCache::~MultiObjectCache() {} - void MultiObjectCache::removeUnreferencedObjectsInCache() { std::vector> objectsToRemove; @@ -44,7 +39,7 @@ namespace Resource _objectCache.clear(); } - void MultiObjectCache::addEntryToObjectCache(const std::string& filename, osg::Object* object) + void MultiObjectCache::addEntryToObjectCache(VFS::Path::NormalizedView filename, osg::Object* object) { if (!object) { @@ -52,23 +47,23 @@ namespace Resource return; } std::lock_guard lock(_objectCacheMutex); - _objectCache.insert(std::make_pair(filename, object)); + _objectCache.emplace(filename, object); } - osg::ref_ptr MultiObjectCache::takeFromObjectCache(const std::string& fileName) + osg::ref_ptr MultiObjectCache::takeFromObjectCache(VFS::Path::NormalizedView fileName) { std::lock_guard lock(_objectCacheMutex); ++mGet; - ObjectCacheMap::iterator found = _objectCache.find(fileName); - if (found == _objectCache.end()) - return osg::ref_ptr(); - else + const auto it = _objectCache.find(fileName); + if (it != _objectCache.end()) { - osg::ref_ptr object = std::move(found->second); - _objectCache.erase(found); + osg::ref_ptr object = std::move(it->second); + _objectCache.erase(it); ++mHit; return object; } + + return nullptr; } void MultiObjectCache::releaseGLObjects(osg::State* state) diff --git a/components/resource/multiobjectcache.hpp b/components/resource/multiobjectcache.hpp index 654a88b524..6e05f462f0 100644 --- a/components/resource/multiobjectcache.hpp +++ b/components/resource/multiobjectcache.hpp @@ -3,11 +3,12 @@ #include #include -#include #include #include +#include + #include "cachestats.hpp" namespace osg @@ -23,18 +24,15 @@ namespace Resource class MultiObjectCache : public osg::Referenced { public: - MultiObjectCache(); - ~MultiObjectCache(); - void removeUnreferencedObjectsInCache(); /** Remove all objects from the cache. */ void clear(); - void addEntryToObjectCache(const std::string& filename, osg::Object* object); + void addEntryToObjectCache(VFS::Path::NormalizedView filename, osg::Object* object); /** Take an Object from cache. Return nullptr if no object found. */ - osg::ref_ptr takeFromObjectCache(const std::string& fileName); + osg::ref_ptr takeFromObjectCache(VFS::Path::NormalizedView fileName); /** call releaseGLObjects on all objects attached to the object cache.*/ void releaseGLObjects(osg::State* state); @@ -42,7 +40,7 @@ namespace Resource CacheStats getStats() const; protected: - typedef std::multimap> ObjectCacheMap; + typedef std::multimap, std::less<>> ObjectCacheMap; ObjectCacheMap _objectCache; mutable std::mutex _objectCacheMutex; From 963b7ec7425ded64ba7a9af8fba3da1d68e2ebe9 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 28 Sep 2024 12:35:45 +0300 Subject: [PATCH 44/45] Unify first/third-person animation choice logic (#8179) Re-enable first-person female/beast-specific animations Use dehardcoded argonian swim animation path --- apps/openmw/mwrender/npcanimation.cpp | 47 +++++++++++++-------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index c8fc36e18a..10bc239dd7 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -484,15 +484,24 @@ namespace MWRender bool is1stPerson = mViewMode == VM_FirstPerson; bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; + std::string_view base; + if (!isWerewolf) + { + if (!is1stPerson) + base = Settings::models().mXbaseanim.get().value(); + else + base = Settings::models().mXbaseanim1st.get().value(); + } + const std::string defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath( getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf), mResourceSystem->getVFS()); std::string smodel = defaultSkeleton; - bool isBase = !isWerewolf; + bool isCustomModel = false; if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) { std::string model = Misc::ResourceHelpers::correctMeshPath(mNpc->mModel); - isBase = isDefaultActorSkeleton(model); + isCustomModel = !isDefaultActorSkeleton(model); smodel = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS()); } @@ -500,33 +509,21 @@ namespace MWRender updateParts(); - if (!is1stPerson) - { - const std::string& base = Settings::models().mXbaseanim.get().value(); - if (!isWerewolf) - addAnimSource(base, smodel); + if (!base.empty()) + addAnimSource(base, smodel); - if (!isBase) - { - addAnimSource(defaultSkeleton, smodel); - addAnimSource(smodel, smodel); - } - else if (base != defaultSkeleton) - { - addAnimSource(defaultSkeleton, smodel); - } + if (defaultSkeleton != base) + addAnimSource(defaultSkeleton, smodel); - if (!isWerewolf && isBeast && mNpc->mRace.contains("argonian")) - addAnimSource("meshes\\xargonian_swimkna.nif", smodel); - } - else - { - if (!isWerewolf) - addAnimSource(Settings::models().mXbaseanim1st.get().value(), smodel); + if (isCustomModel) + addAnimSource(smodel, smodel); - if (!isBase) - addAnimSource(smodel, smodel); + const bool customArgonianSwim = !is1stPerson && !isWerewolf && isBeast && mNpc->mRace.contains("argonian"); + if (customArgonianSwim) + addAnimSource(Settings::models().mXargonianswimkna.get().value(), smodel); + if (is1stPerson) + { mObjectRoot->setNodeMask(Mask_FirstPerson); mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView)); } From 481e63ffa7d8932cf22fb7252ffc1d670f7509eb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 29 Sep 2024 22:36:31 +0200 Subject: [PATCH 45/45] Prevent stale pointers in UI widgets --- apps/openmw/mwlua/luamanagerimp.cpp | 5 +++++ apps/openmw/mwlua/luamanagerimp.hpp | 1 + components/lua_ui/element.cpp | 35 +++++++++++++++++++++++------ components/lua_ui/widget.cpp | 1 + components/lua_ui/widget.hpp | 2 +- 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index cf55ac63e6..9f45fdb744 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -238,6 +238,11 @@ namespace MWLua } void LuaManager::synchronizedUpdate() + { + mLua.protectedCall([&](LuaUtil::LuaView&) { synchronizedUpdateUnsafe(); }); + } + + void LuaManager::synchronizedUpdateUnsafe() { if (mNewGameStarted) { diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 5fa20d377f..d75b033a43 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -170,6 +170,7 @@ namespace MWLua LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, std::optional autoStartConf = std::nullopt); void reloadAllScriptsImpl(); + void synchronizedUpdateUnsafe(); bool mInitialized = false; bool mGlobalScriptsStarted = false; diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index f3f873a583..2d8462e273 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -113,6 +113,7 @@ namespace LuaUi ContentView content(LuaUtil::cast(contentObj)); result.resize(content.size()); size_t minSize = std::min(children.size(), content.size()); + std::vector toDestroy; for (size_t i = 0; i < minSize; i++) { WidgetExtension* ext = children[i]; @@ -121,7 +122,7 @@ namespace LuaUi { WidgetExtension* root = pluckElementRoot(child, depth); if (ext != root) - destroyChild(ext); + toDestroy.emplace_back(ext); result[i] = root; } else @@ -133,14 +134,12 @@ namespace LuaUi } else { - destroyChild(ext); + toDestroy.emplace_back(ext); ext = createWidget(newLayout, false, depth); } result[i] = ext; } } - for (size_t i = minSize; i < children.size(); i++) - destroyChild(children[i]); for (size_t i = minSize; i < content.size(); i++) { sol::object child = content.at(i); @@ -149,6 +148,11 @@ namespace LuaUi else result[i] = createWidget(child.as(), false, depth); } + // Don't destroy anything until element creation has had a chance to throw + for (size_t i = minSize; i < children.size(); i++) + destroyChild(children[i]); + for (WidgetExtension* ext : toDestroy) + destroyChild(ext); return result; } @@ -217,7 +221,9 @@ namespace LuaUi std::string setLayer(WidgetExtension* ext, const sol::table& layout) { MyGUI::ILayer* layerNode = ext->widget()->getLayer(); - std::string currentLayer = layerNode ? layerNode->getName() : std::string(); + std::string_view currentLayer; + if (layerNode) + currentLayer = layerNode->getName(); std::string newLayer = layout.get_or(LayoutKeys::layer, std::string()); if (!newLayer.empty() && !MyGUI::LayerManager::getInstance().isExist(newLayer)) throw std::logic_error(std::string("Layer ") + newLayer + " doesn't exist"); @@ -278,9 +284,20 @@ namespace LuaUi WidgetExtension* parent = mRoot->getParent(); auto children = parent->children(); auto it = std::find(children.begin(), children.end(), mRoot); - mRoot = createWidget(layout(), true, 0); assert(it != children.end()); - *it = mRoot; + try + { + mRoot = createWidget(layout(), true, 0); + *it = mRoot; + } + catch (...) + { + // Remove mRoot from its parent's children even if we couldn't replace it + children.erase(it); + parent->setChildren(children); + mRoot = nullptr; + throw; + } parent->setChildren(children); mRoot->updateCoord(); } @@ -300,6 +317,10 @@ namespace LuaUi { if (mRoot != nullptr) { + // If someone decided to destroy an element used as another element's content, we need to detach it + // first so the parent doesn't end up holding a stale pointer + if (WidgetExtension* parent = mRoot->getParent()) + parent->detachChildrenIf([&](WidgetExtension* child) { return child == mRoot; }); destroyRoot(mRoot); mRoot = nullptr; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index e7e1053ab7..71416be8c8 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -126,6 +126,7 @@ namespace LuaUi { mParent = nullptr; widget()->detachFromWidget(); + widget()->detachFromLayer(); } WidgetExtension* WidgetExtension::findDeep(std::string_view flagName) diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 24962f6820..0ec688d3bb 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -179,7 +179,7 @@ namespace LuaUi void updateVisible(); - void detachChildrenIf(auto&& predicate, std::vector children) + void detachChildrenIf(auto&& predicate, std::vector& children) { for (auto it = children.begin(); it != children.end();) {