From 0ac0c62091a2e7762b22bac4b0975cb3e6e5d6c3 Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 2 Apr 2025 10:19:07 +0000 Subject: [PATCH 01/55] Replace openmw_spellcreation_dialog.layout --- .../mygui/openmw_spellcreation_dialog.layout | 191 +++++++++--------- 1 file changed, 98 insertions(+), 93 deletions(-) diff --git a/files/data/mygui/openmw_spellcreation_dialog.layout b/files/data/mygui/openmw_spellcreation_dialog.layout index c284927a37..fafe4635e7 100644 --- a/files/data/mygui/openmw_spellcreation_dialog.layout +++ b/files/data/mygui/openmw_spellcreation_dialog.layout @@ -1,93 +1,98 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 15a525676c5ec503edf2650c0bd019f7f4197be3 Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 2 Apr 2025 10:38:53 +0000 Subject: [PATCH 02/55] Replace spellcreationdialog.cpp to add gold counter for player --- apps/openmw/mwgui/spellcreationdialog.cpp | 1521 +++++++++++---------- 1 file changed, 763 insertions(+), 758 deletions(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index d8302df87c..86c46c61e3 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -1,758 +1,763 @@ -#include "spellcreationdialog.hpp" - -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/windowmanager.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/containerstore.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwworld/store.hpp" - -#include "../mwmechanics/actorutil.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/spellutil.hpp" - -#include "class.hpp" -#include "tooltips.hpp" -#include "widgets.hpp" - -namespace -{ - - bool sortMagicEffects(short id1, short id2) - { - const MWWorld::Store& gmst - = MWBase::Environment::get().getESMStore()->get(); - - return gmst.find(ESM::MagicEffect::indexToGmstString(id1))->mValue.getString() - < gmst.find(ESM::MagicEffect::indexToGmstString(id2))->mValue.getString(); - } - - void init(ESM::ENAMstruct& effect) - { - effect.mArea = 0; - effect.mDuration = 0; - effect.mEffectID = -1; - effect.mMagnMax = 0; - effect.mMagnMin = 0; - effect.mRange = 0; - effect.mSkill = -1; - effect.mAttribute = -1; - } -} - -namespace MWGui -{ - - EditEffectDialog::EditEffectDialog() - : WindowModal("openmw_edit_effect.layout") - , mEditing(false) - , mMagicEffect(nullptr) - , mConstantEffect(false) - { - init(mEffect); - init(mOldEffect); - - getWidget(mCancelButton, "CancelButton"); - getWidget(mOkButton, "OkButton"); - getWidget(mDeleteButton, "DeleteButton"); - getWidget(mRangeButton, "RangeButton"); - getWidget(mMagnitudeMinValue, "MagnitudeMinValue"); - getWidget(mMagnitudeMaxValue, "MagnitudeMaxValue"); - getWidget(mDurationValue, "DurationValue"); - getWidget(mAreaValue, "AreaValue"); - getWidget(mMagnitudeMinSlider, "MagnitudeMinSlider"); - getWidget(mMagnitudeMaxSlider, "MagnitudeMaxSlider"); - getWidget(mDurationSlider, "DurationSlider"); - getWidget(mAreaSlider, "AreaSlider"); - getWidget(mEffectImage, "EffectImage"); - getWidget(mEffectName, "EffectName"); - getWidget(mAreaText, "AreaText"); - getWidget(mDurationBox, "DurationBox"); - getWidget(mAreaBox, "AreaBox"); - getWidget(mMagnitudeBox, "MagnitudeBox"); - - mRangeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onRangeButtonClicked); - mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onOkButtonClicked); - mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onCancelButtonClicked); - mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onDeleteButtonClicked); - - mMagnitudeMinSlider->eventScrollChangePosition - += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMinChanged); - mMagnitudeMaxSlider->eventScrollChangePosition - += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMaxChanged); - mDurationSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onDurationChanged); - mAreaSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onAreaChanged); - } - - void EditEffectDialog::setConstantEffect(bool constant) - { - mConstantEffect = constant; - } - - void EditEffectDialog::onOpen() - { - WindowModal::onOpen(); - center(); - } - - bool EditEffectDialog::exit() - { - if (mEditing) - eventEffectModified(mOldEffect); - else - eventEffectRemoved(mEffect); - return true; - } - - void EditEffectDialog::newEffect(const ESM::MagicEffect* effect) - { - bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; - bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; - - setMagicEffect(effect); - mEditing = false; - - mDeleteButton->setVisible(false); - - mEffect.mRange = ESM::RT_Self; - if (!allowSelf) - mEffect.mRange = ESM::RT_Touch; - if (!allowTouch) - mEffect.mRange = ESM::RT_Target; - mEffect.mMagnMin = 1; - mEffect.mMagnMax = 1; - mEffect.mDuration = 1; - mEffect.mArea = 0; - mEffect.mSkill = -1; - mEffect.mAttribute = -1; - eventEffectAdded(mEffect); - - onRangeButtonClicked(mRangeButton); - - mMagnitudeMinSlider->setScrollPosition(0); - mMagnitudeMaxSlider->setScrollPosition(0); - mAreaSlider->setScrollPosition(0); - mDurationSlider->setScrollPosition(0); - - mDurationValue->setCaption("1"); - mMagnitudeMinValue->setCaption("1"); - const std::string to{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-") }; - - mMagnitudeMaxValue->setCaption(to + " 1"); - mAreaValue->setCaption("0"); - - setVisible(true); - } - - void EditEffectDialog::editEffect(ESM::ENAMstruct effect) - { - const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); - - setMagicEffect(magicEffect); - mOldEffect = effect; - mEffect = effect; - mEditing = true; - - mDeleteButton->setVisible(true); - - mMagnitudeMinSlider->setScrollPosition(effect.mMagnMin - 1); - mMagnitudeMaxSlider->setScrollPosition(effect.mMagnMax - 1); - mAreaSlider->setScrollPosition(effect.mArea); - mDurationSlider->setScrollPosition(effect.mDuration - 1); - - if (mEffect.mRange == ESM::RT_Self) - mRangeButton->setCaptionWithReplacing("#{sRangeSelf}"); - else if (mEffect.mRange == ESM::RT_Target) - mRangeButton->setCaptionWithReplacing("#{sRangeTarget}"); - else if (mEffect.mRange == ESM::RT_Touch) - mRangeButton->setCaptionWithReplacing("#{sRangeTouch}"); - - onMagnitudeMinChanged(mMagnitudeMinSlider, effect.mMagnMin - 1); - onMagnitudeMaxChanged(mMagnitudeMinSlider, effect.mMagnMax - 1); - onAreaChanged(mAreaSlider, effect.mArea); - onDurationChanged(mDurationSlider, effect.mDuration - 1); - eventEffectModified(mEffect); - - updateBoxes(); - } - - void EditEffectDialog::setMagicEffect(const ESM::MagicEffect* effect) - { - mEffectImage->setImageTexture(Misc::ResourceHelpers::correctIconPath( - effect->mIcon, MWBase::Environment::get().getResourceSystem()->getVFS())); - - mEffectName->setCaptionWithReplacing("#{" + ESM::MagicEffect::indexToGmstString(effect->mIndex) + "}"); - - mEffect.mEffectID = effect->mIndex; - - mMagicEffect = effect; - - updateBoxes(); - } - - void EditEffectDialog::updateBoxes() - { - static int startY = mMagnitudeBox->getPosition().top; - int curY = startY; - - mMagnitudeBox->setVisible(false); - mDurationBox->setVisible(false); - mAreaBox->setVisible(false); - - if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) - { - mMagnitudeBox->setPosition(mMagnitudeBox->getPosition().left, curY); - mMagnitudeBox->setVisible(true); - curY += mMagnitudeBox->getSize().height; - } - if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) && mConstantEffect == false) - { - mDurationBox->setPosition(mDurationBox->getPosition().left, curY); - mDurationBox->setVisible(true); - curY += mDurationBox->getSize().height; - } - if (mEffect.mRange != ESM::RT_Self) - { - mAreaBox->setPosition(mAreaBox->getPosition().left, curY); - mAreaBox->setVisible(true); - // curY += mAreaBox->getSize().height; - } - } - - void EditEffectDialog::onRangeButtonClicked(MyGUI::Widget* sender) - { - mEffect.mRange = (mEffect.mRange + 1) % 3; - - // cycle through range types until we find something that's allowed - // does not handle the case where nothing is allowed (this should be prevented before opening the Add Effect - // dialog) - bool allowSelf = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; - bool allowTouch = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; - bool allowTarget = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; - if (mEffect.mRange == ESM::RT_Self && !allowSelf) - mEffect.mRange = (mEffect.mRange + 1) % 3; - if (mEffect.mRange == ESM::RT_Touch && !allowTouch) - mEffect.mRange = (mEffect.mRange + 1) % 3; - if (mEffect.mRange == ESM::RT_Target && !allowTarget) - mEffect.mRange = (mEffect.mRange + 1) % 3; - - if (mEffect.mRange == ESM::RT_Self) - { - mAreaSlider->setScrollPosition(0); - onAreaChanged(mAreaSlider, 0); - } - - if (mEffect.mRange == ESM::RT_Self) - mRangeButton->setCaptionWithReplacing("#{sRangeSelf}"); - else if (mEffect.mRange == ESM::RT_Target) - mRangeButton->setCaptionWithReplacing("#{sRangeTarget}"); - else if (mEffect.mRange == ESM::RT_Touch) - mRangeButton->setCaptionWithReplacing("#{sRangeTouch}"); - - updateBoxes(); - eventEffectModified(mEffect); - } - - void EditEffectDialog::onDeleteButtonClicked(MyGUI::Widget* sender) - { - setVisible(false); - - eventEffectRemoved(mEffect); - } - - void EditEffectDialog::onOkButtonClicked(MyGUI::Widget* sender) - { - setVisible(false); - } - - void EditEffectDialog::onCancelButtonClicked(MyGUI::Widget* sender) - { - setVisible(false); - exit(); - } - - void EditEffectDialog::setSkill(ESM::RefId skill) - { - mEffect.mSkill = ESM::Skill::refIdToIndex(skill); - eventEffectModified(mEffect); - } - - void EditEffectDialog::setAttribute(ESM::RefId attribute) - { - mEffect.mAttribute = ESM::Attribute::refIdToIndex(attribute); - eventEffectModified(mEffect); - } - - void EditEffectDialog::onMagnitudeMinChanged(MyGUI::ScrollBar* sender, size_t pos) - { - mMagnitudeMinValue->setCaption(MyGUI::utility::toString(pos + 1)); - mEffect.mMagnMin = pos + 1; - - // trigger the check again (see below) - onMagnitudeMaxChanged(mMagnitudeMaxSlider, mMagnitudeMaxSlider->getScrollPosition()); - eventEffectModified(mEffect); - } - - void EditEffectDialog::onMagnitudeMaxChanged(MyGUI::ScrollBar* sender, size_t pos) - { - // make sure the max value is actually larger or equal than the min value - size_t magnMin - = std::abs(mEffect.mMagnMin); // should never be < 0, this is just here to avoid the compiler warning - if (pos + 1 < magnMin) - { - pos = mEffect.mMagnMin - 1; - sender->setScrollPosition(pos); - } - - mEffect.mMagnMax = pos + 1; - const std::string to{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-") }; - - mMagnitudeMaxValue->setCaption(to + " " + MyGUI::utility::toString(pos + 1)); - - eventEffectModified(mEffect); - } - - void EditEffectDialog::onDurationChanged(MyGUI::ScrollBar* sender, size_t pos) - { - mDurationValue->setCaption(MyGUI::utility::toString(pos + 1)); - mEffect.mDuration = pos + 1; - eventEffectModified(mEffect); - } - - void EditEffectDialog::onAreaChanged(MyGUI::ScrollBar* sender, size_t pos) - { - mAreaValue->setCaption(MyGUI::utility::toString(pos)); - mEffect.mArea = pos; - eventEffectModified(mEffect); - } - - // ------------------------------------------------------------------------------------------------ - - SpellCreationDialog::SpellCreationDialog() - : WindowBase("openmw_spellcreation_dialog.layout") - , EffectEditorBase(EffectEditorBase::Spellmaking) - { - getWidget(mNameEdit, "NameEdit"); - getWidget(mMagickaCost, "MagickaCost"); - getWidget(mSuccessChance, "SuccessChance"); - getWidget(mAvailableEffectsList, "AvailableEffects"); - getWidget(mUsedEffectsView, "UsedEffects"); - getWidget(mPriceLabel, "PriceLabel"); - getWidget(mBuyButton, "BuyButton"); - getWidget(mCancelButton, "CancelButton"); - - mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onCancelButtonClicked); - mBuyButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onBuyButtonClicked); - mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SpellCreationDialog::onAccept); - - setWidgets(mAvailableEffectsList, mUsedEffectsView); - } - - void SpellCreationDialog::setPtr(const MWWorld::Ptr& actor) - { - if (actor.isEmpty() || !actor.getClass().isActor()) - throw std::runtime_error("Invalid argument in SpellCreationDialog::setPtr"); - - mPtr = actor; - mNameEdit->setCaption({}); - - startEditing(); - } - - void SpellCreationDialog::onCancelButtonClicked(MyGUI::Widget* sender) - { - MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_SpellCreation); - } - - void SpellCreationDialog::onBuyButtonClicked(MyGUI::Widget* sender) - { - if (mEffects.size() <= 0) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage30}"); - return; - } - - if (mNameEdit->getCaption().empty()) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage10}"); - return; - } - - if (mMagickaCost->getCaption() == "0") - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sEnchantmentMenu8}"); - return; - } - - MWWorld::Ptr player = MWMechanics::getPlayer(); - int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - - int price = MyGUI::utility::parseInt(mPriceLabel->getCaption()); - if (price > playerGold) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage18}"); - return; - } - - mSpell.mName = mNameEdit->getCaption(); - - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price); - - // add gold to NPC trading gold pool - MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); - npcStats.setGoldPool(npcStats.getGoldPool() + price); - - MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Mysticism Hit")); - - const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->insert(mSpell); - - MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - MWMechanics::Spells& spells = stats.getSpells(); - spells.add(spell->mId); - - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellCreation); - } - - void SpellCreationDialog::onAccept(MyGUI::EditBox* sender) - { - onBuyButtonClicked(sender); - - // To do not spam onAccept() again and again - MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); - } - - void SpellCreationDialog::onOpen() - { - center(); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit); - } - - void SpellCreationDialog::onReferenceUnavailable() - { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellCreation); - } - - void SpellCreationDialog::notifyEffectsChanged() - { - if (mEffects.empty()) - { - mMagickaCost->setCaption("0"); - mPriceLabel->setCaption("0"); - mSuccessChance->setCaption("0"); - return; - } - - float y = 0; - - const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - - for (const ESM::ENAMstruct& effect : mEffects) - { - y += std::max( - 1.f, MWMechanics::calcEffectCost(effect, nullptr, MWMechanics::EffectCostMethod::PlayerSpell)); - - if (effect.mRange == ESM::RT_Target) - y *= 1.5; - } - - mSpell.mEffects.populate(mEffects); - mSpell.mData.mCost = int(y); - mSpell.mData.mType = ESM::Spell::ST_Spell; - mSpell.mData.mFlags = 0; - - mMagickaCost->setCaption(MyGUI::utility::toString(int(y))); - - float fSpellMakingValueMult = store.get().find("fSpellMakingValueMult")->mValue.getFloat(); - - int price = std::max(1, static_cast(y * fSpellMakingValueMult)); - price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); - - mPriceLabel->setCaption(MyGUI::utility::toString(int(price))); - - float chance = MWMechanics::calcSpellBaseSuccessChance(&mSpell, MWMechanics::getPlayer(), nullptr); - - int intChance = std::min(100, int(chance)); - mSuccessChance->setCaption(MyGUI::utility::toString(intChance)); - } - - // ------------------------------------------------------------------------------------------------ - - EffectEditorBase::EffectEditorBase(Type type) - : mAvailableEffectsList(nullptr) - , mUsedEffectsView(nullptr) - , mAddEffectDialog() - , mSelectedEffect(0) - , mSelectedKnownEffectId(0) - , mConstantEffect(false) - , mType(type) - { - mAddEffectDialog.eventEffectAdded += MyGUI::newDelegate(this, &EffectEditorBase::onEffectAdded); - mAddEffectDialog.eventEffectModified += MyGUI::newDelegate(this, &EffectEditorBase::onEffectModified); - mAddEffectDialog.eventEffectRemoved += MyGUI::newDelegate(this, &EffectEditorBase::onEffectRemoved); - - mAddEffectDialog.setVisible(false); - } - - EffectEditorBase::~EffectEditorBase() {} - - void EffectEditorBase::startEditing() - { - // get the list of magic effects that are known to the player - - MWWorld::Ptr player = MWMechanics::getPlayer(); - MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - MWMechanics::Spells& spells = stats.getSpells(); - - std::vector knownEffects; - - for (const ESM::Spell* spell : spells) - { - // only normal spells count - if (spell->mData.mType != ESM::Spell::ST_Spell) - continue; - - for (const ESM::IndexedENAMstruct& effectInfo : spell->mEffects.mList) - { - int16_t effectId = effectInfo.mData.mEffectID; - const ESM::MagicEffect* effect - = MWBase::Environment::get().getESMStore()->get().find(effectId); - - // skip effects that do not allow spellmaking/enchanting - int requiredFlags - = (mType == Spellmaking) ? ESM::MagicEffect::AllowSpellmaking : ESM::MagicEffect::AllowEnchanting; - if (!(effect->mData.mFlags & requiredFlags)) - continue; - - if (std::find(knownEffects.begin(), knownEffects.end(), effectId) == knownEffects.end()) - knownEffects.push_back(effectId); - } - } - - std::sort(knownEffects.begin(), knownEffects.end(), sortMagicEffects); - - mAvailableEffectsList->clear(); - - int i = 0; - for (const short effectId : knownEffects) - { - mAvailableEffectsList->addItem(MWBase::Environment::get() - .getESMStore() - ->get() - .find(ESM::MagicEffect::indexToGmstString(effectId)) - ->mValue.getString()); - mButtonMapping[i] = effectId; - ++i; - } - mAvailableEffectsList->adjustSize(); - mAvailableEffectsList->scrollToTop(); - - for (const short effectId : knownEffects) - { - const std::string& name = MWBase::Environment::get() - .getESMStore() - ->get() - .find(ESM::MagicEffect::indexToGmstString(effectId)) - ->mValue.getString(); - MyGUI::Widget* w = mAvailableEffectsList->getItemWidget(name); - - ToolTips::createMagicEffectToolTip(w, effectId); - } - - mEffects.clear(); - updateEffectsView(); - } - - void EffectEditorBase::setWidgets(Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView) - { - mAvailableEffectsList = availableEffectsList; - mUsedEffectsView = usedEffectsView; - - mAvailableEffectsList->eventWidgetSelected - += MyGUI::newDelegate(this, &EffectEditorBase::onAvailableEffectClicked); - } - - void EffectEditorBase::onSelectAttribute() - { - const ESM::MagicEffect* effect - = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); - - mAddEffectDialog.newEffect(effect); - mAddEffectDialog.setAttribute(mSelectAttributeDialog->getAttributeId()); - MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectAttributeDialog)); - } - - void EffectEditorBase::onSelectSkill() - { - const ESM::MagicEffect* effect - = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); - - mAddEffectDialog.newEffect(effect); - mAddEffectDialog.setSkill(mSelectSkillDialog->getSkillId()); - MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectSkillDialog)); - } - - void EffectEditorBase::onAttributeOrSkillCancel() - { - if (mSelectSkillDialog != nullptr) - MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectSkillDialog)); - if (mSelectAttributeDialog != nullptr) - MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectAttributeDialog)); - } - - void EffectEditorBase::onAvailableEffectClicked(MyGUI::Widget* sender) - { - if (mEffects.size() >= 8) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage28}"); - return; - } - - int buttonId = *sender->getUserData(); - mSelectedKnownEffectId = mButtonMapping[buttonId]; - - const ESM::MagicEffect* effect - = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); - - bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; - bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; - bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; - - if (!allowSelf && !allowTouch && !allowTarget) - return; // TODO: Show an error message popup? - - if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) - { - mSelectSkillDialog = std::make_unique(); - mSelectSkillDialog->eventCancel += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel); - mSelectSkillDialog->eventItemSelected += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectSkill); - mSelectSkillDialog->setVisible(true); - } - else if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - { - mSelectAttributeDialog = std::make_unique(); - mSelectAttributeDialog->eventCancel - += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel); - mSelectAttributeDialog->eventItemSelected - += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectAttribute); - mSelectAttributeDialog->setVisible(true); - } - else - { - for (const ESM::ENAMstruct& effectInfo : mEffects) - { - if (effectInfo.mEffectID == mSelectedKnownEffectId) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sOnetypeEffectMessage}"); - return; - } - } - - mAddEffectDialog.newEffect(effect); - } - } - - void EffectEditorBase::onEffectModified(ESM::ENAMstruct effect) - { - mEffects[mSelectedEffect] = effect; - - updateEffectsView(); - } - - void EffectEditorBase::onEffectRemoved(ESM::ENAMstruct effect) - { - mEffects.erase(mEffects.begin() + mSelectedEffect); - updateEffectsView(); - } - - void EffectEditorBase::updateEffectsView() - { - MyGUI::EnumeratorWidgetPtr oldWidgets = mUsedEffectsView->getEnumerator(); - MyGUI::Gui::getInstance().destroyWidgets(oldWidgets); - - MyGUI::IntSize size(0, 0); - - int i = 0; - for (const ESM::ENAMstruct& effectInfo : mEffects) - { - Widgets::SpellEffectParams params; - params.mEffectID = effectInfo.mEffectID; - params.mSkill = ESM::Skill::indexToRefId(effectInfo.mSkill); - params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mAttribute); - params.mDuration = effectInfo.mDuration; - params.mMagnMin = effectInfo.mMagnMin; - params.mMagnMax = effectInfo.mMagnMax; - params.mRange = effectInfo.mRange; - params.mArea = effectInfo.mArea; - params.mIsConstant = mConstantEffect; - - MyGUI::Button* button = mUsedEffectsView->createWidget( - {}, MyGUI::IntCoord(0, size.height, 0, 24), MyGUI::Align::Default); - button->setUserData(i); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onEditEffect); - button->setNeedMouseFocus(true); - - Widgets::MWSpellEffectPtr effect = button->createWidget( - "MW_EffectImage", MyGUI::IntCoord(0, 0, 0, 24), MyGUI::Align::Default); - - effect->setNeedMouseFocus(false); - effect->setSpellEffect(params); - - effect->setSize(effect->getRequestedWidth(), 24); - button->setSize(effect->getRequestedWidth(), 24); - - size.width = std::max(size.width, effect->getRequestedWidth()); - size.height += 24; - ++i; - } - - // Canvas size must be expressed with HScroll disabled, otherwise MyGUI would expand the scroll area when the - // scrollbar is hidden - mUsedEffectsView->setVisibleHScroll(false); - mUsedEffectsView->setCanvasSize(size); - mUsedEffectsView->setVisibleHScroll(true); - - notifyEffectsChanged(); - } - - void EffectEditorBase::onEffectAdded(ESM::ENAMstruct effect) - { - mEffects.push_back(effect); - mSelectedEffect = mEffects.size() - 1; - - updateEffectsView(); - } - - void EffectEditorBase::onEditEffect(MyGUI::Widget* sender) - { - int id = *sender->getUserData(); - - mSelectedEffect = id; - - mAddEffectDialog.editEffect(mEffects[id]); - mAddEffectDialog.setVisible(true); - } - - void EffectEditorBase::setConstantEffect(bool constant) - { - mAddEffectDialog.setConstantEffect(constant); - if (!mConstantEffect && constant) - for (ESM::ENAMstruct& effect : mEffects) - effect.mRange = ESM::RT_Self; - mConstantEffect = constant; - } -} +#include "spellcreationdialog.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/store.hpp" + +#include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/spellutil.hpp" + +#include "class.hpp" +#include "tooltips.hpp" +#include "widgets.hpp" + +namespace +{ + + bool sortMagicEffects(short id1, short id2) + { + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); + + return gmst.find(ESM::MagicEffect::indexToGmstString(id1))->mValue.getString() + < gmst.find(ESM::MagicEffect::indexToGmstString(id2))->mValue.getString(); + } + + void init(ESM::ENAMstruct& effect) + { + effect.mArea = 0; + effect.mDuration = 0; + effect.mEffectID = -1; + effect.mMagnMax = 0; + effect.mMagnMin = 0; + effect.mRange = 0; + effect.mSkill = -1; + effect.mAttribute = -1; + } +} + +namespace MWGui +{ + + EditEffectDialog::EditEffectDialog() + : WindowModal("openmw_edit_effect.layout") + , mEditing(false) + , mMagicEffect(nullptr) + , mConstantEffect(false) + { + init(mEffect); + init(mOldEffect); + + getWidget(mCancelButton, "CancelButton"); + getWidget(mOkButton, "OkButton"); + getWidget(mDeleteButton, "DeleteButton"); + getWidget(mRangeButton, "RangeButton"); + getWidget(mMagnitudeMinValue, "MagnitudeMinValue"); + getWidget(mMagnitudeMaxValue, "MagnitudeMaxValue"); + getWidget(mDurationValue, "DurationValue"); + getWidget(mAreaValue, "AreaValue"); + getWidget(mMagnitudeMinSlider, "MagnitudeMinSlider"); + getWidget(mMagnitudeMaxSlider, "MagnitudeMaxSlider"); + getWidget(mDurationSlider, "DurationSlider"); + getWidget(mAreaSlider, "AreaSlider"); + getWidget(mEffectImage, "EffectImage"); + getWidget(mEffectName, "EffectName"); + getWidget(mAreaText, "AreaText"); + getWidget(mDurationBox, "DurationBox"); + getWidget(mAreaBox, "AreaBox"); + getWidget(mMagnitudeBox, "MagnitudeBox"); + + mRangeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onRangeButtonClicked); + mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onOkButtonClicked); + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onCancelButtonClicked); + mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onDeleteButtonClicked); + + mMagnitudeMinSlider->eventScrollChangePosition + += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMinChanged); + mMagnitudeMaxSlider->eventScrollChangePosition + += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMaxChanged); + mDurationSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onDurationChanged); + mAreaSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onAreaChanged); + } + + void EditEffectDialog::setConstantEffect(bool constant) + { + mConstantEffect = constant; + } + + void EditEffectDialog::onOpen() + { + WindowModal::onOpen(); + center(); + } + + bool EditEffectDialog::exit() + { + if (mEditing) + eventEffectModified(mOldEffect); + else + eventEffectRemoved(mEffect); + return true; + } + + void EditEffectDialog::newEffect(const ESM::MagicEffect* effect) + { + bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; + bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; + + setMagicEffect(effect); + mEditing = false; + + mDeleteButton->setVisible(false); + + mEffect.mRange = ESM::RT_Self; + if (!allowSelf) + mEffect.mRange = ESM::RT_Touch; + if (!allowTouch) + mEffect.mRange = ESM::RT_Target; + mEffect.mMagnMin = 1; + mEffect.mMagnMax = 1; + mEffect.mDuration = 1; + mEffect.mArea = 0; + mEffect.mSkill = -1; + mEffect.mAttribute = -1; + eventEffectAdded(mEffect); + + onRangeButtonClicked(mRangeButton); + + mMagnitudeMinSlider->setScrollPosition(0); + mMagnitudeMaxSlider->setScrollPosition(0); + mAreaSlider->setScrollPosition(0); + mDurationSlider->setScrollPosition(0); + + mDurationValue->setCaption("1"); + mMagnitudeMinValue->setCaption("1"); + const std::string to{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-") }; + + mMagnitudeMaxValue->setCaption(to + " 1"); + mAreaValue->setCaption("0"); + + setVisible(true); + } + + void EditEffectDialog::editEffect(ESM::ENAMstruct effect) + { + const ESM::MagicEffect* magicEffect + = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); + + setMagicEffect(magicEffect); + mOldEffect = effect; + mEffect = effect; + mEditing = true; + + mDeleteButton->setVisible(true); + + mMagnitudeMinSlider->setScrollPosition(effect.mMagnMin - 1); + mMagnitudeMaxSlider->setScrollPosition(effect.mMagnMax - 1); + mAreaSlider->setScrollPosition(effect.mArea); + mDurationSlider->setScrollPosition(effect.mDuration - 1); + + if (mEffect.mRange == ESM::RT_Self) + mRangeButton->setCaptionWithReplacing("#{sRangeSelf}"); + else if (mEffect.mRange == ESM::RT_Target) + mRangeButton->setCaptionWithReplacing("#{sRangeTarget}"); + else if (mEffect.mRange == ESM::RT_Touch) + mRangeButton->setCaptionWithReplacing("#{sRangeTouch}"); + + onMagnitudeMinChanged(mMagnitudeMinSlider, effect.mMagnMin - 1); + onMagnitudeMaxChanged(mMagnitudeMinSlider, effect.mMagnMax - 1); + onAreaChanged(mAreaSlider, effect.mArea); + onDurationChanged(mDurationSlider, effect.mDuration - 1); + eventEffectModified(mEffect); + + updateBoxes(); + } + + void EditEffectDialog::setMagicEffect(const ESM::MagicEffect* effect) + { + mEffectImage->setImageTexture(Misc::ResourceHelpers::correctIconPath( + effect->mIcon, MWBase::Environment::get().getResourceSystem()->getVFS())); + + mEffectName->setCaptionWithReplacing("#{" + ESM::MagicEffect::indexToGmstString(effect->mIndex) + "}"); + + mEffect.mEffectID = effect->mIndex; + + mMagicEffect = effect; + + updateBoxes(); + } + + void EditEffectDialog::updateBoxes() + { + static int startY = mMagnitudeBox->getPosition().top; + int curY = startY; + + mMagnitudeBox->setVisible(false); + mDurationBox->setVisible(false); + mAreaBox->setVisible(false); + + if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) + { + mMagnitudeBox->setPosition(mMagnitudeBox->getPosition().left, curY); + mMagnitudeBox->setVisible(true); + curY += mMagnitudeBox->getSize().height; + } + if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) && mConstantEffect == false) + { + mDurationBox->setPosition(mDurationBox->getPosition().left, curY); + mDurationBox->setVisible(true); + curY += mDurationBox->getSize().height; + } + if (mEffect.mRange != ESM::RT_Self) + { + mAreaBox->setPosition(mAreaBox->getPosition().left, curY); + mAreaBox->setVisible(true); + // curY += mAreaBox->getSize().height; + } + } + + void EditEffectDialog::onRangeButtonClicked(MyGUI::Widget* sender) + { + mEffect.mRange = (mEffect.mRange + 1) % 3; + + // cycle through range types until we find something that's allowed + // does not handle the case where nothing is allowed (this should be prevented before opening the Add Effect + // dialog) + bool allowSelf = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; + bool allowTouch = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; + bool allowTarget = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; + if (mEffect.mRange == ESM::RT_Self && !allowSelf) + mEffect.mRange = (mEffect.mRange + 1) % 3; + if (mEffect.mRange == ESM::RT_Touch && !allowTouch) + mEffect.mRange = (mEffect.mRange + 1) % 3; + if (mEffect.mRange == ESM::RT_Target && !allowTarget) + mEffect.mRange = (mEffect.mRange + 1) % 3; + + if (mEffect.mRange == ESM::RT_Self) + { + mAreaSlider->setScrollPosition(0); + onAreaChanged(mAreaSlider, 0); + } + + if (mEffect.mRange == ESM::RT_Self) + mRangeButton->setCaptionWithReplacing("#{sRangeSelf}"); + else if (mEffect.mRange == ESM::RT_Target) + mRangeButton->setCaptionWithReplacing("#{sRangeTarget}"); + else if (mEffect.mRange == ESM::RT_Touch) + mRangeButton->setCaptionWithReplacing("#{sRangeTouch}"); + + updateBoxes(); + eventEffectModified(mEffect); + } + + void EditEffectDialog::onDeleteButtonClicked(MyGUI::Widget* sender) + { + setVisible(false); + + eventEffectRemoved(mEffect); + } + + void EditEffectDialog::onOkButtonClicked(MyGUI::Widget* sender) + { + setVisible(false); + } + + void EditEffectDialog::onCancelButtonClicked(MyGUI::Widget* sender) + { + setVisible(false); + exit(); + } + + void EditEffectDialog::setSkill(ESM::RefId skill) + { + mEffect.mSkill = ESM::Skill::refIdToIndex(skill); + eventEffectModified(mEffect); + } + + void EditEffectDialog::setAttribute(ESM::RefId attribute) + { + mEffect.mAttribute = ESM::Attribute::refIdToIndex(attribute); + eventEffectModified(mEffect); + } + + void EditEffectDialog::onMagnitudeMinChanged(MyGUI::ScrollBar* sender, size_t pos) + { + mMagnitudeMinValue->setCaption(MyGUI::utility::toString(pos + 1)); + mEffect.mMagnMin = pos + 1; + + // trigger the check again (see below) + onMagnitudeMaxChanged(mMagnitudeMaxSlider, mMagnitudeMaxSlider->getScrollPosition()); + eventEffectModified(mEffect); + } + + void EditEffectDialog::onMagnitudeMaxChanged(MyGUI::ScrollBar* sender, size_t pos) + { + // make sure the max value is actually larger or equal than the min value + size_t magnMin + = std::abs(mEffect.mMagnMin); // should never be < 0, this is just here to avoid the compiler warning + if (pos + 1 < magnMin) + { + pos = mEffect.mMagnMin - 1; + sender->setScrollPosition(pos); + } + + mEffect.mMagnMax = pos + 1; + const std::string to{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-") }; + + mMagnitudeMaxValue->setCaption(to + " " + MyGUI::utility::toString(pos + 1)); + + eventEffectModified(mEffect); + } + + void EditEffectDialog::onDurationChanged(MyGUI::ScrollBar* sender, size_t pos) + { + mDurationValue->setCaption(MyGUI::utility::toString(pos + 1)); + mEffect.mDuration = pos + 1; + eventEffectModified(mEffect); + } + + void EditEffectDialog::onAreaChanged(MyGUI::ScrollBar* sender, size_t pos) + { + mAreaValue->setCaption(MyGUI::utility::toString(pos)); + mEffect.mArea = pos; + eventEffectModified(mEffect); + } + + // ------------------------------------------------------------------------------------------------ + + SpellCreationDialog::SpellCreationDialog() + : WindowBase("openmw_spellcreation_dialog.layout") + , EffectEditorBase(EffectEditorBase::Spellmaking) + { + getWidget(mNameEdit, "NameEdit"); + getWidget(mMagickaCost, "MagickaCost"); + getWidget(mSuccessChance, "SuccessChance"); + getWidget(mAvailableEffectsList, "AvailableEffects"); + getWidget(mUsedEffectsView, "UsedEffects"); + getWidget(mPriceLabel, "PriceLabel"); + getWidget(mPlayerGold, "PlayerGold"); + getWidget(mBuyButton, "BuyButton"); + getWidget(mCancelButton, "CancelButton"); + + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onCancelButtonClicked); + mBuyButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onBuyButtonClicked); + mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SpellCreationDialog::onAccept); + + setWidgets(mAvailableEffectsList, mUsedEffectsView); + } + + void SpellCreationDialog::setPtr(const MWWorld::Ptr& actor) + { + if (actor.isEmpty() || !actor.getClass().isActor()) + throw std::runtime_error("Invalid argument in SpellCreationDialog::setPtr"); + + mPtr = actor; + mNameEdit->setCaption({}); + + MWWorld::Ptr player = MWMechanics::getPlayer(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); + + startEditing(); + } + + void SpellCreationDialog::onCancelButtonClicked(MyGUI::Widget* sender) + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_SpellCreation); + } + + void SpellCreationDialog::onBuyButtonClicked(MyGUI::Widget* sender) + { + if (mEffects.size() <= 0) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage30}"); + return; + } + + if (mNameEdit->getCaption().empty()) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage10}"); + return; + } + + if (mMagickaCost->getCaption() == "0") + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sEnchantmentMenu8}"); + return; + } + + MWWorld::Ptr player = MWMechanics::getPlayer(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + + int price = MyGUI::utility::parseInt(mPriceLabel->getCaption()); + if (price > playerGold) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage18}"); + return; + } + + mSpell.mName = mNameEdit->getCaption(); + + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price); + + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); + npcStats.setGoldPool(npcStats.getGoldPool() + price); + + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Mysticism Hit")); + + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->insert(mSpell); + + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + MWMechanics::Spells& spells = stats.getSpells(); + spells.add(spell->mId); + + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellCreation); + } + + void SpellCreationDialog::onAccept(MyGUI::EditBox* sender) + { + onBuyButtonClicked(sender); + + // To do not spam onAccept() again and again + MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); + } + + void SpellCreationDialog::onOpen() + { + center(); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit); + } + + void SpellCreationDialog::onReferenceUnavailable() + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellCreation); + } + + void SpellCreationDialog::notifyEffectsChanged() + { + if (mEffects.empty()) + { + mMagickaCost->setCaption("0"); + mPriceLabel->setCaption("0"); + mSuccessChance->setCaption("0"); + return; + } + + float y = 0; + + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + + for (const ESM::ENAMstruct& effect : mEffects) + { + y += std::max( + 1.f, MWMechanics::calcEffectCost(effect, nullptr, MWMechanics::EffectCostMethod::PlayerSpell)); + + if (effect.mRange == ESM::RT_Target) + y *= 1.5; + } + + mSpell.mEffects.populate(mEffects); + mSpell.mData.mCost = int(y); + mSpell.mData.mType = ESM::Spell::ST_Spell; + mSpell.mData.mFlags = 0; + + mMagickaCost->setCaption(MyGUI::utility::toString(int(y))); + + float fSpellMakingValueMult = store.get().find("fSpellMakingValueMult")->mValue.getFloat(); + + int price = std::max(1, static_cast(y * fSpellMakingValueMult)); + price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); + + mPriceLabel->setCaption(MyGUI::utility::toString(int(price))); + + float chance = MWMechanics::calcSpellBaseSuccessChance(&mSpell, MWMechanics::getPlayer(), nullptr); + + int intChance = std::min(100, int(chance)); + mSuccessChance->setCaption(MyGUI::utility::toString(intChance)); + } + + // ------------------------------------------------------------------------------------------------ + + EffectEditorBase::EffectEditorBase(Type type) + : mAvailableEffectsList(nullptr) + , mUsedEffectsView(nullptr) + , mAddEffectDialog() + , mSelectedEffect(0) + , mSelectedKnownEffectId(0) + , mConstantEffect(false) + , mType(type) + { + mAddEffectDialog.eventEffectAdded += MyGUI::newDelegate(this, &EffectEditorBase::onEffectAdded); + mAddEffectDialog.eventEffectModified += MyGUI::newDelegate(this, &EffectEditorBase::onEffectModified); + mAddEffectDialog.eventEffectRemoved += MyGUI::newDelegate(this, &EffectEditorBase::onEffectRemoved); + + mAddEffectDialog.setVisible(false); + } + + EffectEditorBase::~EffectEditorBase() {} + + void EffectEditorBase::startEditing() + { + // get the list of magic effects that are known to the player + + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + MWMechanics::Spells& spells = stats.getSpells(); + + std::vector knownEffects; + + for (const ESM::Spell* spell : spells) + { + // only normal spells count + if (spell->mData.mType != ESM::Spell::ST_Spell) + continue; + + for (const ESM::IndexedENAMstruct& effectInfo : spell->mEffects.mList) + { + int16_t effectId = effectInfo.mData.mEffectID; + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find(effectId); + + // skip effects that do not allow spellmaking/enchanting + int requiredFlags + = (mType == Spellmaking) ? ESM::MagicEffect::AllowSpellmaking : ESM::MagicEffect::AllowEnchanting; + if (!(effect->mData.mFlags & requiredFlags)) + continue; + + if (std::find(knownEffects.begin(), knownEffects.end(), effectId) == knownEffects.end()) + knownEffects.push_back(effectId); + } + } + + std::sort(knownEffects.begin(), knownEffects.end(), sortMagicEffects); + + mAvailableEffectsList->clear(); + + int i = 0; + for (const short effectId : knownEffects) + { + mAvailableEffectsList->addItem(MWBase::Environment::get() + .getESMStore() + ->get() + .find(ESM::MagicEffect::indexToGmstString(effectId)) + ->mValue.getString()); + mButtonMapping[i] = effectId; + ++i; + } + mAvailableEffectsList->adjustSize(); + mAvailableEffectsList->scrollToTop(); + + for (const short effectId : knownEffects) + { + const std::string& name = MWBase::Environment::get() + .getESMStore() + ->get() + .find(ESM::MagicEffect::indexToGmstString(effectId)) + ->mValue.getString(); + MyGUI::Widget* w = mAvailableEffectsList->getItemWidget(name); + + ToolTips::createMagicEffectToolTip(w, effectId); + } + + mEffects.clear(); + updateEffectsView(); + } + + void EffectEditorBase::setWidgets(Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView) + { + mAvailableEffectsList = availableEffectsList; + mUsedEffectsView = usedEffectsView; + + mAvailableEffectsList->eventWidgetSelected + += MyGUI::newDelegate(this, &EffectEditorBase::onAvailableEffectClicked); + } + + void EffectEditorBase::onSelectAttribute() + { + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); + + mAddEffectDialog.newEffect(effect); + mAddEffectDialog.setAttribute(mSelectAttributeDialog->getAttributeId()); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectAttributeDialog)); + } + + void EffectEditorBase::onSelectSkill() + { + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); + + mAddEffectDialog.newEffect(effect); + mAddEffectDialog.setSkill(mSelectSkillDialog->getSkillId()); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectSkillDialog)); + } + + void EffectEditorBase::onAttributeOrSkillCancel() + { + if (mSelectSkillDialog != nullptr) + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectSkillDialog)); + if (mSelectAttributeDialog != nullptr) + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectAttributeDialog)); + } + + void EffectEditorBase::onAvailableEffectClicked(MyGUI::Widget* sender) + { + if (mEffects.size() >= 8) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage28}"); + return; + } + + int buttonId = *sender->getUserData(); + mSelectedKnownEffectId = mButtonMapping[buttonId]; + + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); + + bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; + bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; + bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; + + if (!allowSelf && !allowTouch && !allowTarget) + return; // TODO: Show an error message popup? + + if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) + { + mSelectSkillDialog = std::make_unique(); + mSelectSkillDialog->eventCancel += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel); + mSelectSkillDialog->eventItemSelected += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectSkill); + mSelectSkillDialog->setVisible(true); + } + else if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute) + { + mSelectAttributeDialog = std::make_unique(); + mSelectAttributeDialog->eventCancel + += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel); + mSelectAttributeDialog->eventItemSelected + += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectAttribute); + mSelectAttributeDialog->setVisible(true); + } + else + { + for (const ESM::ENAMstruct& effectInfo : mEffects) + { + if (effectInfo.mEffectID == mSelectedKnownEffectId) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sOnetypeEffectMessage}"); + return; + } + } + + mAddEffectDialog.newEffect(effect); + } + } + + void EffectEditorBase::onEffectModified(ESM::ENAMstruct effect) + { + mEffects[mSelectedEffect] = effect; + + updateEffectsView(); + } + + void EffectEditorBase::onEffectRemoved(ESM::ENAMstruct effect) + { + mEffects.erase(mEffects.begin() + mSelectedEffect); + updateEffectsView(); + } + + void EffectEditorBase::updateEffectsView() + { + MyGUI::EnumeratorWidgetPtr oldWidgets = mUsedEffectsView->getEnumerator(); + MyGUI::Gui::getInstance().destroyWidgets(oldWidgets); + + MyGUI::IntSize size(0, 0); + + int i = 0; + for (const ESM::ENAMstruct& effectInfo : mEffects) + { + Widgets::SpellEffectParams params; + params.mEffectID = effectInfo.mEffectID; + params.mSkill = ESM::Skill::indexToRefId(effectInfo.mSkill); + params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mAttribute); + params.mDuration = effectInfo.mDuration; + params.mMagnMin = effectInfo.mMagnMin; + params.mMagnMax = effectInfo.mMagnMax; + params.mRange = effectInfo.mRange; + params.mArea = effectInfo.mArea; + params.mIsConstant = mConstantEffect; + + MyGUI::Button* button = mUsedEffectsView->createWidget( + {}, MyGUI::IntCoord(0, size.height, 0, 24), MyGUI::Align::Default); + button->setUserData(i); + button->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onEditEffect); + button->setNeedMouseFocus(true); + + Widgets::MWSpellEffectPtr effect = button->createWidget( + "MW_EffectImage", MyGUI::IntCoord(0, 0, 0, 24), MyGUI::Align::Default); + + effect->setNeedMouseFocus(false); + effect->setSpellEffect(params); + + effect->setSize(effect->getRequestedWidth(), 24); + button->setSize(effect->getRequestedWidth(), 24); + + size.width = std::max(size.width, effect->getRequestedWidth()); + size.height += 24; + ++i; + } + + // Canvas size must be expressed with HScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden + mUsedEffectsView->setVisibleHScroll(false); + mUsedEffectsView->setCanvasSize(size); + mUsedEffectsView->setVisibleHScroll(true); + + notifyEffectsChanged(); + } + + void EffectEditorBase::onEffectAdded(ESM::ENAMstruct effect) + { + mEffects.push_back(effect); + mSelectedEffect = mEffects.size() - 1; + + updateEffectsView(); + } + + void EffectEditorBase::onEditEffect(MyGUI::Widget* sender) + { + int id = *sender->getUserData(); + + mSelectedEffect = id; + + mAddEffectDialog.editEffect(mEffects[id]); + mAddEffectDialog.setVisible(true); + } + + void EffectEditorBase::setConstantEffect(bool constant) + { + mAddEffectDialog.setConstantEffect(constant); + if (!mConstantEffect && constant) + for (ESM::ENAMstruct& effect : mEffects) + effect.mRange = ESM::RT_Self; + mConstantEffect = constant; + } +} From f8c8b9b433c66ffb7e545df4367eab6fab160a90 Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 2 Apr 2025 10:50:05 +0000 Subject: [PATCH 03/55] Replace spellcreationdialog.hpp to add player gold to spell creation window. --- apps/openmw/mwgui/spellcreationdialog.hpp | 375 +++++++++++----------- 1 file changed, 191 insertions(+), 184 deletions(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp index 6dfe61fc57..0ffa875620 100644 --- a/apps/openmw/mwgui/spellcreationdialog.hpp +++ b/apps/openmw/mwgui/spellcreationdialog.hpp @@ -1,184 +1,191 @@ -#ifndef MWGUI_SPELLCREATION_H -#define MWGUI_SPELLCREATION_H - -#include - -#include -#include - -#include "referenceinterface.hpp" -#include "windowbase.hpp" - -namespace Gui -{ - class MWList; -} - -namespace MWGui -{ - - class SelectSkillDialog; - class SelectAttributeDialog; - - class EditEffectDialog : public WindowModal - { - public: - EditEffectDialog(); - - void onOpen() override; - bool exit() override; - - void setConstantEffect(bool constant); - - void setSkill(ESM::RefId skill); - void setAttribute(ESM::RefId attribute); - - void newEffect(const ESM::MagicEffect* effect); - void editEffect(ESM::ENAMstruct effect); - typedef MyGUI::delegates::MultiDelegate EventHandle_Effect; - - EventHandle_Effect eventEffectAdded; - EventHandle_Effect eventEffectModified; - EventHandle_Effect eventEffectRemoved; - - protected: - MyGUI::Button* mCancelButton; - MyGUI::Button* mOkButton; - MyGUI::Button* mDeleteButton; - - MyGUI::Button* mRangeButton; - - MyGUI::Widget* mDurationBox; - MyGUI::Widget* mMagnitudeBox; - MyGUI::Widget* mAreaBox; - - MyGUI::TextBox* mMagnitudeMinValue; - MyGUI::TextBox* mMagnitudeMaxValue; - MyGUI::TextBox* mDurationValue; - MyGUI::TextBox* mAreaValue; - - MyGUI::ScrollBar* mMagnitudeMinSlider; - MyGUI::ScrollBar* mMagnitudeMaxSlider; - MyGUI::ScrollBar* mDurationSlider; - MyGUI::ScrollBar* mAreaSlider; - - MyGUI::TextBox* mAreaText; - - MyGUI::ImageBox* mEffectImage; - MyGUI::TextBox* mEffectName; - - bool mEditing; - - protected: - void onRangeButtonClicked(MyGUI::Widget* sender); - void onDeleteButtonClicked(MyGUI::Widget* sender); - void onOkButtonClicked(MyGUI::Widget* sender); - void onCancelButtonClicked(MyGUI::Widget* sender); - - void onMagnitudeMinChanged(MyGUI::ScrollBar* sender, size_t pos); - void onMagnitudeMaxChanged(MyGUI::ScrollBar* sender, size_t pos); - void onDurationChanged(MyGUI::ScrollBar* sender, size_t pos); - void onAreaChanged(MyGUI::ScrollBar* sender, size_t pos); - void setMagicEffect(const ESM::MagicEffect* effect); - - void updateBoxes(); - - protected: - ESM::ENAMstruct mEffect; - ESM::ENAMstruct mOldEffect; - - const ESM::MagicEffect* mMagicEffect; - - bool mConstantEffect; - }; - - class EffectEditorBase - { - public: - enum Type - { - Spellmaking, - Enchanting - }; - - EffectEditorBase(Type type); - virtual ~EffectEditorBase(); - - void setConstantEffect(bool constant); - - protected: - std::map mButtonMapping; // maps button ID to effect ID - - Gui::MWList* mAvailableEffectsList; - MyGUI::ScrollView* mUsedEffectsView; - - EditEffectDialog mAddEffectDialog; - std::unique_ptr mSelectAttributeDialog; - std::unique_ptr mSelectSkillDialog; - - int mSelectedEffect; - short mSelectedKnownEffectId; - - bool mConstantEffect; - - std::vector mEffects; - - void onEffectAdded(ESM::ENAMstruct effect); - void onEffectModified(ESM::ENAMstruct effect); - void onEffectRemoved(ESM::ENAMstruct effect); - - void onAvailableEffectClicked(MyGUI::Widget* sender); - - void onAttributeOrSkillCancel(); - void onSelectAttribute(); - void onSelectSkill(); - - void onEditEffect(MyGUI::Widget* sender); - - void updateEffectsView(); - - void startEditing(); - void setWidgets(Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView); - - virtual void notifyEffectsChanged() {} - - private: - Type mType; - }; - - class SpellCreationDialog : public WindowBase, public ReferenceInterface, public EffectEditorBase - { - public: - SpellCreationDialog(); - - void onOpen() override; - void clear() override { resetReference(); } - - void onFrame(float dt) override { checkReferenceAvailable(); } - - void setPtr(const MWWorld::Ptr& actor) override; - - std::string_view getWindowIdForLua() const override { return "SpellCreationDialog"; } - - protected: - void onReferenceUnavailable() override; - - void onCancelButtonClicked(MyGUI::Widget* sender); - void onBuyButtonClicked(MyGUI::Widget* sender); - void onAccept(MyGUI::EditBox* sender); - - void notifyEffectsChanged() override; - - MyGUI::EditBox* mNameEdit; - MyGUI::TextBox* mMagickaCost; - MyGUI::TextBox* mSuccessChance; - MyGUI::Button* mBuyButton; - MyGUI::Button* mCancelButton; - MyGUI::TextBox* mPriceLabel; - - ESM::Spell mSpell; - }; - -} - -#endif +#ifndef MWGUI_SPELLCREATION_H +#define MWGUI_SPELLCREATION_H + +#include + +#include +#include + +#include "referenceinterface.hpp" +#include "windowbase.hpp" + +namespace Gui +{ + class MWList; +} + +namespace MWGui +{ + + class SelectSkillDialog; + class SelectAttributeDialog; + + class EditEffectDialog : public WindowModal + { + public: + EditEffectDialog(); + + void onOpen() override; + bool exit() override; + + void setConstantEffect(bool constant); + + void setSkill(ESM::RefId skill); + void setAttribute(ESM::RefId attribute); + + void newEffect(const ESM::MagicEffect* effect); + void editEffect(ESM::ENAMstruct effect); + typedef MyGUI::delegates::MultiDelegate EventHandle_Effect; + + EventHandle_Effect eventEffectAdded; + EventHandle_Effect eventEffectModified; + EventHandle_Effect eventEffectRemoved; + + protected: + MyGUI::Button* mCancelButton; + MyGUI::Button* mOkButton; + MyGUI::Button* mDeleteButton; + + + MyGUI::Button* mRangeButton; + + MyGUI::Widget* mDurationBox; + MyGUI::Widget* mMagnitudeBox; + MyGUI::Widget* mAreaBox; + + MyGUI::TextBox* mMagnitudeMinValue; + MyGUI::TextBox* mMagnitudeMaxValue; + MyGUI::TextBox* mDurationValue; + MyGUI::TextBox* mAreaValue; + + MyGUI::ScrollBar* mMagnitudeMinSlider; + MyGUI::ScrollBar* mMagnitudeMaxSlider; + MyGUI::ScrollBar* mDurationSlider; + MyGUI::ScrollBar* mAreaSlider; + + MyGUI::TextBox* mAreaText; + + MyGUI::ImageBox* mEffectImage; + MyGUI::TextBox* mEffectName; + + bool mEditing; + + protected: + void onRangeButtonClicked(MyGUI::Widget* sender); + void onDeleteButtonClicked(MyGUI::Widget* sender); + void onOkButtonClicked(MyGUI::Widget* sender); + void onCancelButtonClicked(MyGUI::Widget* sender); + + void onMagnitudeMinChanged(MyGUI::ScrollBar* sender, size_t pos); + void onMagnitudeMaxChanged(MyGUI::ScrollBar* sender, size_t pos); + void onDurationChanged(MyGUI::ScrollBar* sender, size_t pos); + void onAreaChanged(MyGUI::ScrollBar* sender, size_t pos); + void setMagicEffect(const ESM::MagicEffect* effect); + + void updateBoxes(); + + protected: + ESM::ENAMstruct mEffect; + ESM::ENAMstruct mOldEffect; + + const ESM::MagicEffect* mMagicEffect; + + bool mConstantEffect; + }; + + class EffectEditorBase + { + public: + enum Type + { + Spellmaking, + Enchanting + }; + + EffectEditorBase(Type type); + virtual ~EffectEditorBase(); + + void setConstantEffect(bool constant); + + + + protected: + std::map mButtonMapping; // maps button ID to effect ID + + Gui::MWList* mAvailableEffectsList; + MyGUI::ScrollView* mUsedEffectsView; + + + EditEffectDialog mAddEffectDialog; + std::unique_ptr mSelectAttributeDialog; + std::unique_ptr mSelectSkillDialog; + + int mSelectedEffect; + short mSelectedKnownEffectId; + + bool mConstantEffect; + + std::vector mEffects; + + void onEffectAdded(ESM::ENAMstruct effect); + void onEffectModified(ESM::ENAMstruct effect); + void onEffectRemoved(ESM::ENAMstruct effect); + + void onAvailableEffectClicked(MyGUI::Widget* sender); + + void onAttributeOrSkillCancel(); + void onSelectAttribute(); + void onSelectSkill(); + + void onEditEffect(MyGUI::Widget* sender); + + void updateEffectsView(); + + void startEditing(); + void setWidgets(Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView); + + virtual void notifyEffectsChanged() {} + + private: + Type mType; + }; + + class SpellCreationDialog : public WindowBase, public ReferenceInterface, public EffectEditorBase + { + public: + SpellCreationDialog(); + + void onOpen() override; + void clear() override { resetReference(); } + + void onFrame(float dt) override { checkReferenceAvailable(); } + + void setPtr(const MWWorld::Ptr& actor) override; + + void updateLabels(); + + std::string_view getWindowIdForLua() const override { return "SpellCreationDialog"; } + + protected: + void onReferenceUnavailable() override; + + void onCancelButtonClicked(MyGUI::Widget* sender); + void onBuyButtonClicked(MyGUI::Widget* sender); + void onAccept(MyGUI::EditBox* sender); + + void notifyEffectsChanged() override; + + MyGUI::EditBox* mNameEdit; + MyGUI::TextBox* mMagickaCost; + MyGUI::TextBox* mSuccessChance; + MyGUI::Button* mBuyButton; + MyGUI::Button* mCancelButton; + MyGUI::TextBox* mPriceLabel; + MyGUI::TextBox* mPlayerGold; + + ESM::Spell mSpell; + }; + +} + +#endif From bcb27fff6a5c966cc2a004dc09362137adea5d2b Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 3 Apr 2025 01:45:28 +0000 Subject: [PATCH 04/55] Update 2 files - /apps/openmw/mwgui/spellcreationdialog.cpp - /apps/openmw/mwgui/spellcreationdialog.hpp --- apps/openmw/mwgui/spellcreationdialog.cpp | 16 ++++++++-------- apps/openmw/mwgui/spellcreationdialog.hpp | 8 +------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 86c46c61e3..884f2136cb 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -83,7 +83,7 @@ namespace MWGui getWidget(mDurationBox, "DurationBox"); getWidget(mAreaBox, "AreaBox"); getWidget(mMagnitudeBox, "MagnitudeBox"); - + mRangeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onRangeButtonClicked); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onOkButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onCancelButtonClicked); @@ -370,13 +370,13 @@ namespace MWGui mPtr = actor; mNameEdit->setCaption({}); - + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); - + startEditing(); - } + } void SpellCreationDialog::onCancelButtonClicked(MyGUI::Widget* sender) { @@ -556,10 +556,10 @@ namespace MWGui for (const short effectId : knownEffects) { mAvailableEffectsList->addItem(MWBase::Environment::get() - .getESMStore() - ->get() - .find(ESM::MagicEffect::indexToGmstString(effectId)) - ->mValue.getString()); + .getESMStore() + ->get() + .find(ESM::MagicEffect::indexToGmstString(effectId)) + ->mValue.getString()); mButtonMapping[i] = effectId; ++i; } diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp index 0ffa875620..b3c6ac51be 100644 --- a/apps/openmw/mwgui/spellcreationdialog.hpp +++ b/apps/openmw/mwgui/spellcreationdialog.hpp @@ -45,7 +45,6 @@ namespace MWGui MyGUI::Button* mCancelButton; MyGUI::Button* mOkButton; MyGUI::Button* mDeleteButton; - MyGUI::Button* mRangeButton; @@ -107,14 +106,11 @@ namespace MWGui void setConstantEffect(bool constant); - - protected: std::map mButtonMapping; // maps button ID to effect ID Gui::MWList* mAvailableEffectsList; MyGUI::ScrollView* mUsedEffectsView; - EditEffectDialog mAddEffectDialog; std::unique_ptr mSelectAttributeDialog; @@ -162,8 +158,6 @@ namespace MWGui void setPtr(const MWWorld::Ptr& actor) override; - void updateLabels(); - std::string_view getWindowIdForLua() const override { return "SpellCreationDialog"; } protected: @@ -182,7 +176,7 @@ namespace MWGui MyGUI::Button* mCancelButton; MyGUI::TextBox* mPriceLabel; MyGUI::TextBox* mPlayerGold; - + ESM::Spell mSpell; }; From 2d2df30f8016ce8e22947facf283e8a10e7ecc19 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 3 Apr 2025 05:12:15 +0000 Subject: [PATCH 05/55] Update file spellcreationdialog.cpp --- apps/openmw/mwgui/spellcreationdialog.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 884f2136cb..bdd1a96d87 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -30,7 +30,6 @@ namespace { - bool sortMagicEffects(short id1, short id2) { const MWWorld::Store& gmst @@ -556,10 +555,10 @@ namespace MWGui for (const short effectId : knownEffects) { mAvailableEffectsList->addItem(MWBase::Environment::get() - .getESMStore() - ->get() - .find(ESM::MagicEffect::indexToGmstString(effectId)) - ->mValue.getString()); + .getESMStore() + ->get() + .find(ESM::MagicEffect::indexToGmstString(effectId)) + ->mValue.getString()); mButtonMapping[i] = effectId; ++i; } From 190a1266a13f71a2893d2652abd926ec90facf7d Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 4 Apr 2025 05:09:18 +0000 Subject: [PATCH 06/55] Update 2 files - /apps/openmw/mwgui/spellcreationdialog.cpp - /apps/openmw/mwgui/spellcreationdialog.hpp --- apps/openmw/mwgui/spellcreationdialog.cpp | 1524 ++++++++++----------- apps/openmw/mwgui/spellcreationdialog.hpp | 370 ++--- 2 files changed, 947 insertions(+), 947 deletions(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index bdd1a96d87..651d3014c1 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -1,762 +1,762 @@ -#include "spellcreationdialog.hpp" - -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/windowmanager.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/containerstore.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwworld/store.hpp" - -#include "../mwmechanics/actorutil.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/spellutil.hpp" - -#include "class.hpp" -#include "tooltips.hpp" -#include "widgets.hpp" - -namespace -{ - bool sortMagicEffects(short id1, short id2) - { - const MWWorld::Store& gmst - = MWBase::Environment::get().getESMStore()->get(); - - return gmst.find(ESM::MagicEffect::indexToGmstString(id1))->mValue.getString() - < gmst.find(ESM::MagicEffect::indexToGmstString(id2))->mValue.getString(); - } - - void init(ESM::ENAMstruct& effect) - { - effect.mArea = 0; - effect.mDuration = 0; - effect.mEffectID = -1; - effect.mMagnMax = 0; - effect.mMagnMin = 0; - effect.mRange = 0; - effect.mSkill = -1; - effect.mAttribute = -1; - } -} - -namespace MWGui -{ - - EditEffectDialog::EditEffectDialog() - : WindowModal("openmw_edit_effect.layout") - , mEditing(false) - , mMagicEffect(nullptr) - , mConstantEffect(false) - { - init(mEffect); - init(mOldEffect); - - getWidget(mCancelButton, "CancelButton"); - getWidget(mOkButton, "OkButton"); - getWidget(mDeleteButton, "DeleteButton"); - getWidget(mRangeButton, "RangeButton"); - getWidget(mMagnitudeMinValue, "MagnitudeMinValue"); - getWidget(mMagnitudeMaxValue, "MagnitudeMaxValue"); - getWidget(mDurationValue, "DurationValue"); - getWidget(mAreaValue, "AreaValue"); - getWidget(mMagnitudeMinSlider, "MagnitudeMinSlider"); - getWidget(mMagnitudeMaxSlider, "MagnitudeMaxSlider"); - getWidget(mDurationSlider, "DurationSlider"); - getWidget(mAreaSlider, "AreaSlider"); - getWidget(mEffectImage, "EffectImage"); - getWidget(mEffectName, "EffectName"); - getWidget(mAreaText, "AreaText"); - getWidget(mDurationBox, "DurationBox"); - getWidget(mAreaBox, "AreaBox"); - getWidget(mMagnitudeBox, "MagnitudeBox"); - - mRangeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onRangeButtonClicked); - mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onOkButtonClicked); - mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onCancelButtonClicked); - mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onDeleteButtonClicked); - - mMagnitudeMinSlider->eventScrollChangePosition - += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMinChanged); - mMagnitudeMaxSlider->eventScrollChangePosition - += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMaxChanged); - mDurationSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onDurationChanged); - mAreaSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onAreaChanged); - } - - void EditEffectDialog::setConstantEffect(bool constant) - { - mConstantEffect = constant; - } - - void EditEffectDialog::onOpen() - { - WindowModal::onOpen(); - center(); - } - - bool EditEffectDialog::exit() - { - if (mEditing) - eventEffectModified(mOldEffect); - else - eventEffectRemoved(mEffect); - return true; - } - - void EditEffectDialog::newEffect(const ESM::MagicEffect* effect) - { - bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; - bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; - - setMagicEffect(effect); - mEditing = false; - - mDeleteButton->setVisible(false); - - mEffect.mRange = ESM::RT_Self; - if (!allowSelf) - mEffect.mRange = ESM::RT_Touch; - if (!allowTouch) - mEffect.mRange = ESM::RT_Target; - mEffect.mMagnMin = 1; - mEffect.mMagnMax = 1; - mEffect.mDuration = 1; - mEffect.mArea = 0; - mEffect.mSkill = -1; - mEffect.mAttribute = -1; - eventEffectAdded(mEffect); - - onRangeButtonClicked(mRangeButton); - - mMagnitudeMinSlider->setScrollPosition(0); - mMagnitudeMaxSlider->setScrollPosition(0); - mAreaSlider->setScrollPosition(0); - mDurationSlider->setScrollPosition(0); - - mDurationValue->setCaption("1"); - mMagnitudeMinValue->setCaption("1"); - const std::string to{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-") }; - - mMagnitudeMaxValue->setCaption(to + " 1"); - mAreaValue->setCaption("0"); - - setVisible(true); - } - - void EditEffectDialog::editEffect(ESM::ENAMstruct effect) - { - const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); - - setMagicEffect(magicEffect); - mOldEffect = effect; - mEffect = effect; - mEditing = true; - - mDeleteButton->setVisible(true); - - mMagnitudeMinSlider->setScrollPosition(effect.mMagnMin - 1); - mMagnitudeMaxSlider->setScrollPosition(effect.mMagnMax - 1); - mAreaSlider->setScrollPosition(effect.mArea); - mDurationSlider->setScrollPosition(effect.mDuration - 1); - - if (mEffect.mRange == ESM::RT_Self) - mRangeButton->setCaptionWithReplacing("#{sRangeSelf}"); - else if (mEffect.mRange == ESM::RT_Target) - mRangeButton->setCaptionWithReplacing("#{sRangeTarget}"); - else if (mEffect.mRange == ESM::RT_Touch) - mRangeButton->setCaptionWithReplacing("#{sRangeTouch}"); - - onMagnitudeMinChanged(mMagnitudeMinSlider, effect.mMagnMin - 1); - onMagnitudeMaxChanged(mMagnitudeMinSlider, effect.mMagnMax - 1); - onAreaChanged(mAreaSlider, effect.mArea); - onDurationChanged(mDurationSlider, effect.mDuration - 1); - eventEffectModified(mEffect); - - updateBoxes(); - } - - void EditEffectDialog::setMagicEffect(const ESM::MagicEffect* effect) - { - mEffectImage->setImageTexture(Misc::ResourceHelpers::correctIconPath( - effect->mIcon, MWBase::Environment::get().getResourceSystem()->getVFS())); - - mEffectName->setCaptionWithReplacing("#{" + ESM::MagicEffect::indexToGmstString(effect->mIndex) + "}"); - - mEffect.mEffectID = effect->mIndex; - - mMagicEffect = effect; - - updateBoxes(); - } - - void EditEffectDialog::updateBoxes() - { - static int startY = mMagnitudeBox->getPosition().top; - int curY = startY; - - mMagnitudeBox->setVisible(false); - mDurationBox->setVisible(false); - mAreaBox->setVisible(false); - - if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) - { - mMagnitudeBox->setPosition(mMagnitudeBox->getPosition().left, curY); - mMagnitudeBox->setVisible(true); - curY += mMagnitudeBox->getSize().height; - } - if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) && mConstantEffect == false) - { - mDurationBox->setPosition(mDurationBox->getPosition().left, curY); - mDurationBox->setVisible(true); - curY += mDurationBox->getSize().height; - } - if (mEffect.mRange != ESM::RT_Self) - { - mAreaBox->setPosition(mAreaBox->getPosition().left, curY); - mAreaBox->setVisible(true); - // curY += mAreaBox->getSize().height; - } - } - - void EditEffectDialog::onRangeButtonClicked(MyGUI::Widget* sender) - { - mEffect.mRange = (mEffect.mRange + 1) % 3; - - // cycle through range types until we find something that's allowed - // does not handle the case where nothing is allowed (this should be prevented before opening the Add Effect - // dialog) - bool allowSelf = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; - bool allowTouch = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; - bool allowTarget = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; - if (mEffect.mRange == ESM::RT_Self && !allowSelf) - mEffect.mRange = (mEffect.mRange + 1) % 3; - if (mEffect.mRange == ESM::RT_Touch && !allowTouch) - mEffect.mRange = (mEffect.mRange + 1) % 3; - if (mEffect.mRange == ESM::RT_Target && !allowTarget) - mEffect.mRange = (mEffect.mRange + 1) % 3; - - if (mEffect.mRange == ESM::RT_Self) - { - mAreaSlider->setScrollPosition(0); - onAreaChanged(mAreaSlider, 0); - } - - if (mEffect.mRange == ESM::RT_Self) - mRangeButton->setCaptionWithReplacing("#{sRangeSelf}"); - else if (mEffect.mRange == ESM::RT_Target) - mRangeButton->setCaptionWithReplacing("#{sRangeTarget}"); - else if (mEffect.mRange == ESM::RT_Touch) - mRangeButton->setCaptionWithReplacing("#{sRangeTouch}"); - - updateBoxes(); - eventEffectModified(mEffect); - } - - void EditEffectDialog::onDeleteButtonClicked(MyGUI::Widget* sender) - { - setVisible(false); - - eventEffectRemoved(mEffect); - } - - void EditEffectDialog::onOkButtonClicked(MyGUI::Widget* sender) - { - setVisible(false); - } - - void EditEffectDialog::onCancelButtonClicked(MyGUI::Widget* sender) - { - setVisible(false); - exit(); - } - - void EditEffectDialog::setSkill(ESM::RefId skill) - { - mEffect.mSkill = ESM::Skill::refIdToIndex(skill); - eventEffectModified(mEffect); - } - - void EditEffectDialog::setAttribute(ESM::RefId attribute) - { - mEffect.mAttribute = ESM::Attribute::refIdToIndex(attribute); - eventEffectModified(mEffect); - } - - void EditEffectDialog::onMagnitudeMinChanged(MyGUI::ScrollBar* sender, size_t pos) - { - mMagnitudeMinValue->setCaption(MyGUI::utility::toString(pos + 1)); - mEffect.mMagnMin = pos + 1; - - // trigger the check again (see below) - onMagnitudeMaxChanged(mMagnitudeMaxSlider, mMagnitudeMaxSlider->getScrollPosition()); - eventEffectModified(mEffect); - } - - void EditEffectDialog::onMagnitudeMaxChanged(MyGUI::ScrollBar* sender, size_t pos) - { - // make sure the max value is actually larger or equal than the min value - size_t magnMin - = std::abs(mEffect.mMagnMin); // should never be < 0, this is just here to avoid the compiler warning - if (pos + 1 < magnMin) - { - pos = mEffect.mMagnMin - 1; - sender->setScrollPosition(pos); - } - - mEffect.mMagnMax = pos + 1; - const std::string to{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-") }; - - mMagnitudeMaxValue->setCaption(to + " " + MyGUI::utility::toString(pos + 1)); - - eventEffectModified(mEffect); - } - - void EditEffectDialog::onDurationChanged(MyGUI::ScrollBar* sender, size_t pos) - { - mDurationValue->setCaption(MyGUI::utility::toString(pos + 1)); - mEffect.mDuration = pos + 1; - eventEffectModified(mEffect); - } - - void EditEffectDialog::onAreaChanged(MyGUI::ScrollBar* sender, size_t pos) - { - mAreaValue->setCaption(MyGUI::utility::toString(pos)); - mEffect.mArea = pos; - eventEffectModified(mEffect); - } - - // ------------------------------------------------------------------------------------------------ - - SpellCreationDialog::SpellCreationDialog() - : WindowBase("openmw_spellcreation_dialog.layout") - , EffectEditorBase(EffectEditorBase::Spellmaking) - { - getWidget(mNameEdit, "NameEdit"); - getWidget(mMagickaCost, "MagickaCost"); - getWidget(mSuccessChance, "SuccessChance"); - getWidget(mAvailableEffectsList, "AvailableEffects"); - getWidget(mUsedEffectsView, "UsedEffects"); - getWidget(mPriceLabel, "PriceLabel"); - getWidget(mPlayerGold, "PlayerGold"); - getWidget(mBuyButton, "BuyButton"); - getWidget(mCancelButton, "CancelButton"); - - mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onCancelButtonClicked); - mBuyButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onBuyButtonClicked); - mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SpellCreationDialog::onAccept); - - setWidgets(mAvailableEffectsList, mUsedEffectsView); - } - - void SpellCreationDialog::setPtr(const MWWorld::Ptr& actor) - { - if (actor.isEmpty() || !actor.getClass().isActor()) - throw std::runtime_error("Invalid argument in SpellCreationDialog::setPtr"); - - mPtr = actor; - mNameEdit->setCaption({}); - - MWWorld::Ptr player = MWMechanics::getPlayer(); - int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); - - startEditing(); - } - - void SpellCreationDialog::onCancelButtonClicked(MyGUI::Widget* sender) - { - MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_SpellCreation); - } - - void SpellCreationDialog::onBuyButtonClicked(MyGUI::Widget* sender) - { - if (mEffects.size() <= 0) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage30}"); - return; - } - - if (mNameEdit->getCaption().empty()) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage10}"); - return; - } - - if (mMagickaCost->getCaption() == "0") - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sEnchantmentMenu8}"); - return; - } - - MWWorld::Ptr player = MWMechanics::getPlayer(); - int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - - int price = MyGUI::utility::parseInt(mPriceLabel->getCaption()); - if (price > playerGold) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage18}"); - return; - } - - mSpell.mName = mNameEdit->getCaption(); - - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price); - - // add gold to NPC trading gold pool - MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); - npcStats.setGoldPool(npcStats.getGoldPool() + price); - - MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Mysticism Hit")); - - const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->insert(mSpell); - - MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - MWMechanics::Spells& spells = stats.getSpells(); - spells.add(spell->mId); - - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellCreation); - } - - void SpellCreationDialog::onAccept(MyGUI::EditBox* sender) - { - onBuyButtonClicked(sender); - - // To do not spam onAccept() again and again - MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); - } - - void SpellCreationDialog::onOpen() - { - center(); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit); - } - - void SpellCreationDialog::onReferenceUnavailable() - { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellCreation); - } - - void SpellCreationDialog::notifyEffectsChanged() - { - if (mEffects.empty()) - { - mMagickaCost->setCaption("0"); - mPriceLabel->setCaption("0"); - mSuccessChance->setCaption("0"); - return; - } - - float y = 0; - - const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - - for (const ESM::ENAMstruct& effect : mEffects) - { - y += std::max( - 1.f, MWMechanics::calcEffectCost(effect, nullptr, MWMechanics::EffectCostMethod::PlayerSpell)); - - if (effect.mRange == ESM::RT_Target) - y *= 1.5; - } - - mSpell.mEffects.populate(mEffects); - mSpell.mData.mCost = int(y); - mSpell.mData.mType = ESM::Spell::ST_Spell; - mSpell.mData.mFlags = 0; - - mMagickaCost->setCaption(MyGUI::utility::toString(int(y))); - - float fSpellMakingValueMult = store.get().find("fSpellMakingValueMult")->mValue.getFloat(); - - int price = std::max(1, static_cast(y * fSpellMakingValueMult)); - price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); - - mPriceLabel->setCaption(MyGUI::utility::toString(int(price))); - - float chance = MWMechanics::calcSpellBaseSuccessChance(&mSpell, MWMechanics::getPlayer(), nullptr); - - int intChance = std::min(100, int(chance)); - mSuccessChance->setCaption(MyGUI::utility::toString(intChance)); - } - - // ------------------------------------------------------------------------------------------------ - - EffectEditorBase::EffectEditorBase(Type type) - : mAvailableEffectsList(nullptr) - , mUsedEffectsView(nullptr) - , mAddEffectDialog() - , mSelectedEffect(0) - , mSelectedKnownEffectId(0) - , mConstantEffect(false) - , mType(type) - { - mAddEffectDialog.eventEffectAdded += MyGUI::newDelegate(this, &EffectEditorBase::onEffectAdded); - mAddEffectDialog.eventEffectModified += MyGUI::newDelegate(this, &EffectEditorBase::onEffectModified); - mAddEffectDialog.eventEffectRemoved += MyGUI::newDelegate(this, &EffectEditorBase::onEffectRemoved); - - mAddEffectDialog.setVisible(false); - } - - EffectEditorBase::~EffectEditorBase() {} - - void EffectEditorBase::startEditing() - { - // get the list of magic effects that are known to the player - - MWWorld::Ptr player = MWMechanics::getPlayer(); - MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - MWMechanics::Spells& spells = stats.getSpells(); - - std::vector knownEffects; - - for (const ESM::Spell* spell : spells) - { - // only normal spells count - if (spell->mData.mType != ESM::Spell::ST_Spell) - continue; - - for (const ESM::IndexedENAMstruct& effectInfo : spell->mEffects.mList) - { - int16_t effectId = effectInfo.mData.mEffectID; - const ESM::MagicEffect* effect - = MWBase::Environment::get().getESMStore()->get().find(effectId); - - // skip effects that do not allow spellmaking/enchanting - int requiredFlags - = (mType == Spellmaking) ? ESM::MagicEffect::AllowSpellmaking : ESM::MagicEffect::AllowEnchanting; - if (!(effect->mData.mFlags & requiredFlags)) - continue; - - if (std::find(knownEffects.begin(), knownEffects.end(), effectId) == knownEffects.end()) - knownEffects.push_back(effectId); - } - } - - std::sort(knownEffects.begin(), knownEffects.end(), sortMagicEffects); - - mAvailableEffectsList->clear(); - - int i = 0; - for (const short effectId : knownEffects) - { - mAvailableEffectsList->addItem(MWBase::Environment::get() - .getESMStore() - ->get() - .find(ESM::MagicEffect::indexToGmstString(effectId)) - ->mValue.getString()); - mButtonMapping[i] = effectId; - ++i; - } - mAvailableEffectsList->adjustSize(); - mAvailableEffectsList->scrollToTop(); - - for (const short effectId : knownEffects) - { - const std::string& name = MWBase::Environment::get() - .getESMStore() - ->get() - .find(ESM::MagicEffect::indexToGmstString(effectId)) - ->mValue.getString(); - MyGUI::Widget* w = mAvailableEffectsList->getItemWidget(name); - - ToolTips::createMagicEffectToolTip(w, effectId); - } - - mEffects.clear(); - updateEffectsView(); - } - - void EffectEditorBase::setWidgets(Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView) - { - mAvailableEffectsList = availableEffectsList; - mUsedEffectsView = usedEffectsView; - - mAvailableEffectsList->eventWidgetSelected - += MyGUI::newDelegate(this, &EffectEditorBase::onAvailableEffectClicked); - } - - void EffectEditorBase::onSelectAttribute() - { - const ESM::MagicEffect* effect - = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); - - mAddEffectDialog.newEffect(effect); - mAddEffectDialog.setAttribute(mSelectAttributeDialog->getAttributeId()); - MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectAttributeDialog)); - } - - void EffectEditorBase::onSelectSkill() - { - const ESM::MagicEffect* effect - = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); - - mAddEffectDialog.newEffect(effect); - mAddEffectDialog.setSkill(mSelectSkillDialog->getSkillId()); - MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectSkillDialog)); - } - - void EffectEditorBase::onAttributeOrSkillCancel() - { - if (mSelectSkillDialog != nullptr) - MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectSkillDialog)); - if (mSelectAttributeDialog != nullptr) - MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectAttributeDialog)); - } - - void EffectEditorBase::onAvailableEffectClicked(MyGUI::Widget* sender) - { - if (mEffects.size() >= 8) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage28}"); - return; - } - - int buttonId = *sender->getUserData(); - mSelectedKnownEffectId = mButtonMapping[buttonId]; - - const ESM::MagicEffect* effect - = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); - - bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; - bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; - bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; - - if (!allowSelf && !allowTouch && !allowTarget) - return; // TODO: Show an error message popup? - - if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) - { - mSelectSkillDialog = std::make_unique(); - mSelectSkillDialog->eventCancel += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel); - mSelectSkillDialog->eventItemSelected += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectSkill); - mSelectSkillDialog->setVisible(true); - } - else if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - { - mSelectAttributeDialog = std::make_unique(); - mSelectAttributeDialog->eventCancel - += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel); - mSelectAttributeDialog->eventItemSelected - += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectAttribute); - mSelectAttributeDialog->setVisible(true); - } - else - { - for (const ESM::ENAMstruct& effectInfo : mEffects) - { - if (effectInfo.mEffectID == mSelectedKnownEffectId) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sOnetypeEffectMessage}"); - return; - } - } - - mAddEffectDialog.newEffect(effect); - } - } - - void EffectEditorBase::onEffectModified(ESM::ENAMstruct effect) - { - mEffects[mSelectedEffect] = effect; - - updateEffectsView(); - } - - void EffectEditorBase::onEffectRemoved(ESM::ENAMstruct effect) - { - mEffects.erase(mEffects.begin() + mSelectedEffect); - updateEffectsView(); - } - - void EffectEditorBase::updateEffectsView() - { - MyGUI::EnumeratorWidgetPtr oldWidgets = mUsedEffectsView->getEnumerator(); - MyGUI::Gui::getInstance().destroyWidgets(oldWidgets); - - MyGUI::IntSize size(0, 0); - - int i = 0; - for (const ESM::ENAMstruct& effectInfo : mEffects) - { - Widgets::SpellEffectParams params; - params.mEffectID = effectInfo.mEffectID; - params.mSkill = ESM::Skill::indexToRefId(effectInfo.mSkill); - params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mAttribute); - params.mDuration = effectInfo.mDuration; - params.mMagnMin = effectInfo.mMagnMin; - params.mMagnMax = effectInfo.mMagnMax; - params.mRange = effectInfo.mRange; - params.mArea = effectInfo.mArea; - params.mIsConstant = mConstantEffect; - - MyGUI::Button* button = mUsedEffectsView->createWidget( - {}, MyGUI::IntCoord(0, size.height, 0, 24), MyGUI::Align::Default); - button->setUserData(i); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onEditEffect); - button->setNeedMouseFocus(true); - - Widgets::MWSpellEffectPtr effect = button->createWidget( - "MW_EffectImage", MyGUI::IntCoord(0, 0, 0, 24), MyGUI::Align::Default); - - effect->setNeedMouseFocus(false); - effect->setSpellEffect(params); - - effect->setSize(effect->getRequestedWidth(), 24); - button->setSize(effect->getRequestedWidth(), 24); - - size.width = std::max(size.width, effect->getRequestedWidth()); - size.height += 24; - ++i; - } - - // Canvas size must be expressed with HScroll disabled, otherwise MyGUI would expand the scroll area when the - // scrollbar is hidden - mUsedEffectsView->setVisibleHScroll(false); - mUsedEffectsView->setCanvasSize(size); - mUsedEffectsView->setVisibleHScroll(true); - - notifyEffectsChanged(); - } - - void EffectEditorBase::onEffectAdded(ESM::ENAMstruct effect) - { - mEffects.push_back(effect); - mSelectedEffect = mEffects.size() - 1; - - updateEffectsView(); - } - - void EffectEditorBase::onEditEffect(MyGUI::Widget* sender) - { - int id = *sender->getUserData(); - - mSelectedEffect = id; - - mAddEffectDialog.editEffect(mEffects[id]); - mAddEffectDialog.setVisible(true); - } - - void EffectEditorBase::setConstantEffect(bool constant) - { - mAddEffectDialog.setConstantEffect(constant); - if (!mConstantEffect && constant) - for (ESM::ENAMstruct& effect : mEffects) - effect.mRange = ESM::RT_Self; - mConstantEffect = constant; - } -} +#include "spellcreationdialog.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/store.hpp" + +#include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/spellutil.hpp" + +#include "class.hpp" +#include "tooltips.hpp" +#include "widgets.hpp" + +namespace +{ + bool sortMagicEffects(short id1, short id2) + { + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); + + return gmst.find(ESM::MagicEffect::indexToGmstString(id1))->mValue.getString() + < gmst.find(ESM::MagicEffect::indexToGmstString(id2))->mValue.getString(); + } + + void init(ESM::ENAMstruct& effect) + { + effect.mArea = 0; + effect.mDuration = 0; + effect.mEffectID = -1; + effect.mMagnMax = 0; + effect.mMagnMin = 0; + effect.mRange = 0; + effect.mSkill = -1; + effect.mAttribute = -1; + } +} + +namespace MWGui +{ + + EditEffectDialog::EditEffectDialog() + : WindowModal("openmw_edit_effect.layout") + , mEditing(false) + , mMagicEffect(nullptr) + , mConstantEffect(false) + { + init(mEffect); + init(mOldEffect); + + getWidget(mCancelButton, "CancelButton"); + getWidget(mOkButton, "OkButton"); + getWidget(mDeleteButton, "DeleteButton"); + getWidget(mRangeButton, "RangeButton"); + getWidget(mMagnitudeMinValue, "MagnitudeMinValue"); + getWidget(mMagnitudeMaxValue, "MagnitudeMaxValue"); + getWidget(mDurationValue, "DurationValue"); + getWidget(mAreaValue, "AreaValue"); + getWidget(mMagnitudeMinSlider, "MagnitudeMinSlider"); + getWidget(mMagnitudeMaxSlider, "MagnitudeMaxSlider"); + getWidget(mDurationSlider, "DurationSlider"); + getWidget(mAreaSlider, "AreaSlider"); + getWidget(mEffectImage, "EffectImage"); + getWidget(mEffectName, "EffectName"); + getWidget(mAreaText, "AreaText"); + getWidget(mDurationBox, "DurationBox"); + getWidget(mAreaBox, "AreaBox"); + getWidget(mMagnitudeBox, "MagnitudeBox"); + + mRangeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onRangeButtonClicked); + mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onOkButtonClicked); + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onCancelButtonClicked); + mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onDeleteButtonClicked); + + mMagnitudeMinSlider->eventScrollChangePosition + += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMinChanged); + mMagnitudeMaxSlider->eventScrollChangePosition + += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMaxChanged); + mDurationSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onDurationChanged); + mAreaSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onAreaChanged); + } + + void EditEffectDialog::setConstantEffect(bool constant) + { + mConstantEffect = constant; + } + + void EditEffectDialog::onOpen() + { + WindowModal::onOpen(); + center(); + } + + bool EditEffectDialog::exit() + { + if (mEditing) + eventEffectModified(mOldEffect); + else + eventEffectRemoved(mEffect); + return true; + } + + void EditEffectDialog::newEffect(const ESM::MagicEffect* effect) + { + bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; + bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; + + setMagicEffect(effect); + mEditing = false; + + mDeleteButton->setVisible(false); + + mEffect.mRange = ESM::RT_Self; + if (!allowSelf) + mEffect.mRange = ESM::RT_Touch; + if (!allowTouch) + mEffect.mRange = ESM::RT_Target; + mEffect.mMagnMin = 1; + mEffect.mMagnMax = 1; + mEffect.mDuration = 1; + mEffect.mArea = 0; + mEffect.mSkill = -1; + mEffect.mAttribute = -1; + eventEffectAdded(mEffect); + + onRangeButtonClicked(mRangeButton); + + mMagnitudeMinSlider->setScrollPosition(0); + mMagnitudeMaxSlider->setScrollPosition(0); + mAreaSlider->setScrollPosition(0); + mDurationSlider->setScrollPosition(0); + + mDurationValue->setCaption("1"); + mMagnitudeMinValue->setCaption("1"); + const std::string to{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-") }; + + mMagnitudeMaxValue->setCaption(to + " 1"); + mAreaValue->setCaption("0"); + + setVisible(true); + } + + void EditEffectDialog::editEffect(ESM::ENAMstruct effect) + { + const ESM::MagicEffect* magicEffect + = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); + + setMagicEffect(magicEffect); + mOldEffect = effect; + mEffect = effect; + mEditing = true; + + mDeleteButton->setVisible(true); + + mMagnitudeMinSlider->setScrollPosition(effect.mMagnMin - 1); + mMagnitudeMaxSlider->setScrollPosition(effect.mMagnMax - 1); + mAreaSlider->setScrollPosition(effect.mArea); + mDurationSlider->setScrollPosition(effect.mDuration - 1); + + if (mEffect.mRange == ESM::RT_Self) + mRangeButton->setCaptionWithReplacing("#{sRangeSelf}"); + else if (mEffect.mRange == ESM::RT_Target) + mRangeButton->setCaptionWithReplacing("#{sRangeTarget}"); + else if (mEffect.mRange == ESM::RT_Touch) + mRangeButton->setCaptionWithReplacing("#{sRangeTouch}"); + + onMagnitudeMinChanged(mMagnitudeMinSlider, effect.mMagnMin - 1); + onMagnitudeMaxChanged(mMagnitudeMinSlider, effect.mMagnMax - 1); + onAreaChanged(mAreaSlider, effect.mArea); + onDurationChanged(mDurationSlider, effect.mDuration - 1); + eventEffectModified(mEffect); + + updateBoxes(); + } + + void EditEffectDialog::setMagicEffect(const ESM::MagicEffect* effect) + { + mEffectImage->setImageTexture(Misc::ResourceHelpers::correctIconPath( + effect->mIcon, MWBase::Environment::get().getResourceSystem()->getVFS())); + + mEffectName->setCaptionWithReplacing("#{" + ESM::MagicEffect::indexToGmstString(effect->mIndex) + "}"); + + mEffect.mEffectID = effect->mIndex; + + mMagicEffect = effect; + + updateBoxes(); + } + + void EditEffectDialog::updateBoxes() + { + static int startY = mMagnitudeBox->getPosition().top; + int curY = startY; + + mMagnitudeBox->setVisible(false); + mDurationBox->setVisible(false); + mAreaBox->setVisible(false); + + if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) + { + mMagnitudeBox->setPosition(mMagnitudeBox->getPosition().left, curY); + mMagnitudeBox->setVisible(true); + curY += mMagnitudeBox->getSize().height; + } + if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) && mConstantEffect == false) + { + mDurationBox->setPosition(mDurationBox->getPosition().left, curY); + mDurationBox->setVisible(true); + curY += mDurationBox->getSize().height; + } + if (mEffect.mRange != ESM::RT_Self) + { + mAreaBox->setPosition(mAreaBox->getPosition().left, curY); + mAreaBox->setVisible(true); + // curY += mAreaBox->getSize().height; + } + } + + void EditEffectDialog::onRangeButtonClicked(MyGUI::Widget* sender) + { + mEffect.mRange = (mEffect.mRange + 1) % 3; + + // cycle through range types until we find something that's allowed + // does not handle the case where nothing is allowed (this should be prevented before opening the Add Effect + // dialog) + bool allowSelf = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; + bool allowTouch = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; + bool allowTarget = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; + if (mEffect.mRange == ESM::RT_Self && !allowSelf) + mEffect.mRange = (mEffect.mRange + 1) % 3; + if (mEffect.mRange == ESM::RT_Touch && !allowTouch) + mEffect.mRange = (mEffect.mRange + 1) % 3; + if (mEffect.mRange == ESM::RT_Target && !allowTarget) + mEffect.mRange = (mEffect.mRange + 1) % 3; + + if (mEffect.mRange == ESM::RT_Self) + { + mAreaSlider->setScrollPosition(0); + onAreaChanged(mAreaSlider, 0); + } + + if (mEffect.mRange == ESM::RT_Self) + mRangeButton->setCaptionWithReplacing("#{sRangeSelf}"); + else if (mEffect.mRange == ESM::RT_Target) + mRangeButton->setCaptionWithReplacing("#{sRangeTarget}"); + else if (mEffect.mRange == ESM::RT_Touch) + mRangeButton->setCaptionWithReplacing("#{sRangeTouch}"); + + updateBoxes(); + eventEffectModified(mEffect); + } + + void EditEffectDialog::onDeleteButtonClicked(MyGUI::Widget* sender) + { + setVisible(false); + + eventEffectRemoved(mEffect); + } + + void EditEffectDialog::onOkButtonClicked(MyGUI::Widget* sender) + { + setVisible(false); + } + + void EditEffectDialog::onCancelButtonClicked(MyGUI::Widget* sender) + { + setVisible(false); + exit(); + } + + void EditEffectDialog::setSkill(ESM::RefId skill) + { + mEffect.mSkill = ESM::Skill::refIdToIndex(skill); + eventEffectModified(mEffect); + } + + void EditEffectDialog::setAttribute(ESM::RefId attribute) + { + mEffect.mAttribute = ESM::Attribute::refIdToIndex(attribute); + eventEffectModified(mEffect); + } + + void EditEffectDialog::onMagnitudeMinChanged(MyGUI::ScrollBar* sender, size_t pos) + { + mMagnitudeMinValue->setCaption(MyGUI::utility::toString(pos + 1)); + mEffect.mMagnMin = pos + 1; + + // trigger the check again (see below) + onMagnitudeMaxChanged(mMagnitudeMaxSlider, mMagnitudeMaxSlider->getScrollPosition()); + eventEffectModified(mEffect); + } + + void EditEffectDialog::onMagnitudeMaxChanged(MyGUI::ScrollBar* sender, size_t pos) + { + // make sure the max value is actually larger or equal than the min value + size_t magnMin + = std::abs(mEffect.mMagnMin); // should never be < 0, this is just here to avoid the compiler warning + if (pos + 1 < magnMin) + { + pos = mEffect.mMagnMin - 1; + sender->setScrollPosition(pos); + } + + mEffect.mMagnMax = pos + 1; + const std::string to{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-") }; + + mMagnitudeMaxValue->setCaption(to + " " + MyGUI::utility::toString(pos + 1)); + + eventEffectModified(mEffect); + } + + void EditEffectDialog::onDurationChanged(MyGUI::ScrollBar* sender, size_t pos) + { + mDurationValue->setCaption(MyGUI::utility::toString(pos + 1)); + mEffect.mDuration = pos + 1; + eventEffectModified(mEffect); + } + + void EditEffectDialog::onAreaChanged(MyGUI::ScrollBar* sender, size_t pos) + { + mAreaValue->setCaption(MyGUI::utility::toString(pos)); + mEffect.mArea = pos; + eventEffectModified(mEffect); + } + + // ------------------------------------------------------------------------------------------------ + + SpellCreationDialog::SpellCreationDialog() + : WindowBase("openmw_spellcreation_dialog.layout") + , EffectEditorBase(EffectEditorBase::Spellmaking) + { + getWidget(mNameEdit, "NameEdit"); + getWidget(mMagickaCost, "MagickaCost"); + getWidget(mSuccessChance, "SuccessChance"); + getWidget(mAvailableEffectsList, "AvailableEffects"); + getWidget(mUsedEffectsView, "UsedEffects"); + getWidget(mPriceLabel, "PriceLabel"); + getWidget(mPlayerGold, "PlayerGold"); + getWidget(mBuyButton, "BuyButton"); + getWidget(mCancelButton, "CancelButton"); + + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onCancelButtonClicked); + mBuyButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onBuyButtonClicked); + mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SpellCreationDialog::onAccept); + + setWidgets(mAvailableEffectsList, mUsedEffectsView); + } + + void SpellCreationDialog::setPtr(const MWWorld::Ptr& actor) + { + if (actor.isEmpty() || !actor.getClass().isActor()) + throw std::runtime_error("Invalid argument in SpellCreationDialog::setPtr"); + + mPtr = actor; + mNameEdit->setCaption({}); + + MWWorld::Ptr player = MWMechanics::getPlayer(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); + + startEditing(); + } + + void SpellCreationDialog::onCancelButtonClicked(MyGUI::Widget* sender) + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_SpellCreation); + } + + void SpellCreationDialog::onBuyButtonClicked(MyGUI::Widget* sender) + { + if (mEffects.size() <= 0) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage30}"); + return; + } + + if (mNameEdit->getCaption().empty()) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage10}"); + return; + } + + if (mMagickaCost->getCaption() == "0") + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sEnchantmentMenu8}"); + return; + } + + MWWorld::Ptr player = MWMechanics::getPlayer(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + + int price = MyGUI::utility::parseInt(mPriceLabel->getCaption()); + if (price > playerGold) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage18}"); + return; + } + + mSpell.mName = mNameEdit->getCaption(); + + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price); + + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); + npcStats.setGoldPool(npcStats.getGoldPool() + price); + + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Mysticism Hit")); + + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->insert(mSpell); + + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + MWMechanics::Spells& spells = stats.getSpells(); + spells.add(spell->mId); + + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellCreation); + } + + void SpellCreationDialog::onAccept(MyGUI::EditBox* sender) + { + onBuyButtonClicked(sender); + + // To do not spam onAccept() again and again + MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); + } + + void SpellCreationDialog::onOpen() + { + center(); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit); + } + + void SpellCreationDialog::onReferenceUnavailable() + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellCreation); + } + + void SpellCreationDialog::notifyEffectsChanged() + { + if (mEffects.empty()) + { + mMagickaCost->setCaption("0"); + mPriceLabel->setCaption("0"); + mSuccessChance->setCaption("0"); + return; + } + + float y = 0; + + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + + for (const ESM::ENAMstruct& effect : mEffects) + { + y += std::max( + 1.f, MWMechanics::calcEffectCost(effect, nullptr, MWMechanics::EffectCostMethod::PlayerSpell)); + + if (effect.mRange == ESM::RT_Target) + y *= 1.5; + } + + mSpell.mEffects.populate(mEffects); + mSpell.mData.mCost = int(y); + mSpell.mData.mType = ESM::Spell::ST_Spell; + mSpell.mData.mFlags = 0; + + mMagickaCost->setCaption(MyGUI::utility::toString(int(y))); + + float fSpellMakingValueMult = store.get().find("fSpellMakingValueMult")->mValue.getFloat(); + + int price = std::max(1, static_cast(y * fSpellMakingValueMult)); + price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); + + mPriceLabel->setCaption(MyGUI::utility::toString(int(price))); + + float chance = MWMechanics::calcSpellBaseSuccessChance(&mSpell, MWMechanics::getPlayer(), nullptr); + + int intChance = std::min(100, int(chance)); + mSuccessChance->setCaption(MyGUI::utility::toString(intChance)); + } + + // ------------------------------------------------------------------------------------------------ + + EffectEditorBase::EffectEditorBase(Type type) + : mAvailableEffectsList(nullptr) + , mUsedEffectsView(nullptr) + , mAddEffectDialog() + , mSelectedEffect(0) + , mSelectedKnownEffectId(0) + , mConstantEffect(false) + , mType(type) + { + mAddEffectDialog.eventEffectAdded += MyGUI::newDelegate(this, &EffectEditorBase::onEffectAdded); + mAddEffectDialog.eventEffectModified += MyGUI::newDelegate(this, &EffectEditorBase::onEffectModified); + mAddEffectDialog.eventEffectRemoved += MyGUI::newDelegate(this, &EffectEditorBase::onEffectRemoved); + + mAddEffectDialog.setVisible(false); + } + + EffectEditorBase::~EffectEditorBase() {} + + void EffectEditorBase::startEditing() + { + // get the list of magic effects that are known to the player + + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + MWMechanics::Spells& spells = stats.getSpells(); + + std::vector knownEffects; + + for (const ESM::Spell* spell : spells) + { + // only normal spells count + if (spell->mData.mType != ESM::Spell::ST_Spell) + continue; + + for (const ESM::IndexedENAMstruct& effectInfo : spell->mEffects.mList) + { + int16_t effectId = effectInfo.mData.mEffectID; + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find(effectId); + + // skip effects that do not allow spellmaking/enchanting + int requiredFlags + = (mType == Spellmaking) ? ESM::MagicEffect::AllowSpellmaking : ESM::MagicEffect::AllowEnchanting; + if (!(effect->mData.mFlags & requiredFlags)) + continue; + + if (std::find(knownEffects.begin(), knownEffects.end(), effectId) == knownEffects.end()) + knownEffects.push_back(effectId); + } + } + + std::sort(knownEffects.begin(), knownEffects.end(), sortMagicEffects); + + mAvailableEffectsList->clear(); + + int i = 0; + for (const short effectId : knownEffects) + { + mAvailableEffectsList->addItem(MWBase::Environment::get() + .getESMStore() + ->get() + .find(ESM::MagicEffect::indexToGmstString(effectId)) + ->mValue.getString()); + mButtonMapping[i] = effectId; + ++i; + } + mAvailableEffectsList->adjustSize(); + mAvailableEffectsList->scrollToTop(); + + for (const short effectId : knownEffects) + { + const std::string& name = MWBase::Environment::get() + .getESMStore() + ->get() + .find(ESM::MagicEffect::indexToGmstString(effectId)) + ->mValue.getString(); + MyGUI::Widget* w = mAvailableEffectsList->getItemWidget(name); + + ToolTips::createMagicEffectToolTip(w, effectId); + } + + mEffects.clear(); + updateEffectsView(); + } + + void EffectEditorBase::setWidgets(Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView) + { + mAvailableEffectsList = availableEffectsList; + mUsedEffectsView = usedEffectsView; + + mAvailableEffectsList->eventWidgetSelected + += MyGUI::newDelegate(this, &EffectEditorBase::onAvailableEffectClicked); + } + + void EffectEditorBase::onSelectAttribute() + { + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); + + mAddEffectDialog.newEffect(effect); + mAddEffectDialog.setAttribute(mSelectAttributeDialog->getAttributeId()); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectAttributeDialog)); + } + + void EffectEditorBase::onSelectSkill() + { + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); + + mAddEffectDialog.newEffect(effect); + mAddEffectDialog.setSkill(mSelectSkillDialog->getSkillId()); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectSkillDialog)); + } + + void EffectEditorBase::onAttributeOrSkillCancel() + { + if (mSelectSkillDialog != nullptr) + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectSkillDialog)); + if (mSelectAttributeDialog != nullptr) + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectAttributeDialog)); + } + + void EffectEditorBase::onAvailableEffectClicked(MyGUI::Widget* sender) + { + if (mEffects.size() >= 8) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage28}"); + return; + } + + int buttonId = *sender->getUserData(); + mSelectedKnownEffectId = mButtonMapping[buttonId]; + + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); + + bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; + bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; + bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; + + if (!allowSelf && !allowTouch && !allowTarget) + return; // TODO: Show an error message popup? + + if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) + { + mSelectSkillDialog = std::make_unique(); + mSelectSkillDialog->eventCancel += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel); + mSelectSkillDialog->eventItemSelected += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectSkill); + mSelectSkillDialog->setVisible(true); + } + else if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute) + { + mSelectAttributeDialog = std::make_unique(); + mSelectAttributeDialog->eventCancel + += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel); + mSelectAttributeDialog->eventItemSelected + += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectAttribute); + mSelectAttributeDialog->setVisible(true); + } + else + { + for (const ESM::ENAMstruct& effectInfo : mEffects) + { + if (effectInfo.mEffectID == mSelectedKnownEffectId) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sOnetypeEffectMessage}"); + return; + } + } + + mAddEffectDialog.newEffect(effect); + } + } + + void EffectEditorBase::onEffectModified(ESM::ENAMstruct effect) + { + mEffects[mSelectedEffect] = effect; + + updateEffectsView(); + } + + void EffectEditorBase::onEffectRemoved(ESM::ENAMstruct effect) + { + mEffects.erase(mEffects.begin() + mSelectedEffect); + updateEffectsView(); + } + + void EffectEditorBase::updateEffectsView() + { + MyGUI::EnumeratorWidgetPtr oldWidgets = mUsedEffectsView->getEnumerator(); + MyGUI::Gui::getInstance().destroyWidgets(oldWidgets); + + MyGUI::IntSize size(0, 0); + + int i = 0; + for (const ESM::ENAMstruct& effectInfo : mEffects) + { + Widgets::SpellEffectParams params; + params.mEffectID = effectInfo.mEffectID; + params.mSkill = ESM::Skill::indexToRefId(effectInfo.mSkill); + params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mAttribute); + params.mDuration = effectInfo.mDuration; + params.mMagnMin = effectInfo.mMagnMin; + params.mMagnMax = effectInfo.mMagnMax; + params.mRange = effectInfo.mRange; + params.mArea = effectInfo.mArea; + params.mIsConstant = mConstantEffect; + + MyGUI::Button* button = mUsedEffectsView->createWidget( + {}, MyGUI::IntCoord(0, size.height, 0, 24), MyGUI::Align::Default); + button->setUserData(i); + button->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onEditEffect); + button->setNeedMouseFocus(true); + + Widgets::MWSpellEffectPtr effect = button->createWidget( + "MW_EffectImage", MyGUI::IntCoord(0, 0, 0, 24), MyGUI::Align::Default); + + effect->setNeedMouseFocus(false); + effect->setSpellEffect(params); + + effect->setSize(effect->getRequestedWidth(), 24); + button->setSize(effect->getRequestedWidth(), 24); + + size.width = std::max(size.width, effect->getRequestedWidth()); + size.height += 24; + ++i; + } + + // Canvas size must be expressed with HScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden + mUsedEffectsView->setVisibleHScroll(false); + mUsedEffectsView->setCanvasSize(size); + mUsedEffectsView->setVisibleHScroll(true); + + notifyEffectsChanged(); + } + + void EffectEditorBase::onEffectAdded(ESM::ENAMstruct effect) + { + mEffects.push_back(effect); + mSelectedEffect = mEffects.size() - 1; + + updateEffectsView(); + } + + void EffectEditorBase::onEditEffect(MyGUI::Widget* sender) + { + int id = *sender->getUserData(); + + mSelectedEffect = id; + + mAddEffectDialog.editEffect(mEffects[id]); + mAddEffectDialog.setVisible(true); + } + + void EffectEditorBase::setConstantEffect(bool constant) + { + mAddEffectDialog.setConstantEffect(constant); + if (!mConstantEffect && constant) + for (ESM::ENAMstruct& effect : mEffects) + effect.mRange = ESM::RT_Self; + mConstantEffect = constant; + } +} diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp index b3c6ac51be..0887dd8c94 100644 --- a/apps/openmw/mwgui/spellcreationdialog.hpp +++ b/apps/openmw/mwgui/spellcreationdialog.hpp @@ -1,185 +1,185 @@ -#ifndef MWGUI_SPELLCREATION_H -#define MWGUI_SPELLCREATION_H - -#include - -#include -#include - -#include "referenceinterface.hpp" -#include "windowbase.hpp" - -namespace Gui -{ - class MWList; -} - -namespace MWGui -{ - - class SelectSkillDialog; - class SelectAttributeDialog; - - class EditEffectDialog : public WindowModal - { - public: - EditEffectDialog(); - - void onOpen() override; - bool exit() override; - - void setConstantEffect(bool constant); - - void setSkill(ESM::RefId skill); - void setAttribute(ESM::RefId attribute); - - void newEffect(const ESM::MagicEffect* effect); - void editEffect(ESM::ENAMstruct effect); - typedef MyGUI::delegates::MultiDelegate EventHandle_Effect; - - EventHandle_Effect eventEffectAdded; - EventHandle_Effect eventEffectModified; - EventHandle_Effect eventEffectRemoved; - - protected: - MyGUI::Button* mCancelButton; - MyGUI::Button* mOkButton; - MyGUI::Button* mDeleteButton; - - MyGUI::Button* mRangeButton; - - MyGUI::Widget* mDurationBox; - MyGUI::Widget* mMagnitudeBox; - MyGUI::Widget* mAreaBox; - - MyGUI::TextBox* mMagnitudeMinValue; - MyGUI::TextBox* mMagnitudeMaxValue; - MyGUI::TextBox* mDurationValue; - MyGUI::TextBox* mAreaValue; - - MyGUI::ScrollBar* mMagnitudeMinSlider; - MyGUI::ScrollBar* mMagnitudeMaxSlider; - MyGUI::ScrollBar* mDurationSlider; - MyGUI::ScrollBar* mAreaSlider; - - MyGUI::TextBox* mAreaText; - - MyGUI::ImageBox* mEffectImage; - MyGUI::TextBox* mEffectName; - - bool mEditing; - - protected: - void onRangeButtonClicked(MyGUI::Widget* sender); - void onDeleteButtonClicked(MyGUI::Widget* sender); - void onOkButtonClicked(MyGUI::Widget* sender); - void onCancelButtonClicked(MyGUI::Widget* sender); - - void onMagnitudeMinChanged(MyGUI::ScrollBar* sender, size_t pos); - void onMagnitudeMaxChanged(MyGUI::ScrollBar* sender, size_t pos); - void onDurationChanged(MyGUI::ScrollBar* sender, size_t pos); - void onAreaChanged(MyGUI::ScrollBar* sender, size_t pos); - void setMagicEffect(const ESM::MagicEffect* effect); - - void updateBoxes(); - - protected: - ESM::ENAMstruct mEffect; - ESM::ENAMstruct mOldEffect; - - const ESM::MagicEffect* mMagicEffect; - - bool mConstantEffect; - }; - - class EffectEditorBase - { - public: - enum Type - { - Spellmaking, - Enchanting - }; - - EffectEditorBase(Type type); - virtual ~EffectEditorBase(); - - void setConstantEffect(bool constant); - - protected: - std::map mButtonMapping; // maps button ID to effect ID - - Gui::MWList* mAvailableEffectsList; - MyGUI::ScrollView* mUsedEffectsView; - - EditEffectDialog mAddEffectDialog; - std::unique_ptr mSelectAttributeDialog; - std::unique_ptr mSelectSkillDialog; - - int mSelectedEffect; - short mSelectedKnownEffectId; - - bool mConstantEffect; - - std::vector mEffects; - - void onEffectAdded(ESM::ENAMstruct effect); - void onEffectModified(ESM::ENAMstruct effect); - void onEffectRemoved(ESM::ENAMstruct effect); - - void onAvailableEffectClicked(MyGUI::Widget* sender); - - void onAttributeOrSkillCancel(); - void onSelectAttribute(); - void onSelectSkill(); - - void onEditEffect(MyGUI::Widget* sender); - - void updateEffectsView(); - - void startEditing(); - void setWidgets(Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView); - - virtual void notifyEffectsChanged() {} - - private: - Type mType; - }; - - class SpellCreationDialog : public WindowBase, public ReferenceInterface, public EffectEditorBase - { - public: - SpellCreationDialog(); - - void onOpen() override; - void clear() override { resetReference(); } - - void onFrame(float dt) override { checkReferenceAvailable(); } - - void setPtr(const MWWorld::Ptr& actor) override; - - std::string_view getWindowIdForLua() const override { return "SpellCreationDialog"; } - - protected: - void onReferenceUnavailable() override; - - void onCancelButtonClicked(MyGUI::Widget* sender); - void onBuyButtonClicked(MyGUI::Widget* sender); - void onAccept(MyGUI::EditBox* sender); - - void notifyEffectsChanged() override; - - MyGUI::EditBox* mNameEdit; - MyGUI::TextBox* mMagickaCost; - MyGUI::TextBox* mSuccessChance; - MyGUI::Button* mBuyButton; - MyGUI::Button* mCancelButton; - MyGUI::TextBox* mPriceLabel; - MyGUI::TextBox* mPlayerGold; - - ESM::Spell mSpell; - }; - -} - -#endif +#ifndef MWGUI_SPELLCREATION_H +#define MWGUI_SPELLCREATION_H + +#include + +#include +#include + +#include "referenceinterface.hpp" +#include "windowbase.hpp" + +namespace Gui +{ + class MWList; +} + +namespace MWGui +{ + + class SelectSkillDialog; + class SelectAttributeDialog; + + class EditEffectDialog : public WindowModal + { + public: + EditEffectDialog(); + + void onOpen() override; + bool exit() override; + + void setConstantEffect(bool constant); + + void setSkill(ESM::RefId skill); + void setAttribute(ESM::RefId attribute); + + void newEffect(const ESM::MagicEffect* effect); + void editEffect(ESM::ENAMstruct effect); + typedef MyGUI::delegates::MultiDelegate EventHandle_Effect; + + EventHandle_Effect eventEffectAdded; + EventHandle_Effect eventEffectModified; + EventHandle_Effect eventEffectRemoved; + + protected: + MyGUI::Button* mCancelButton; + MyGUI::Button* mOkButton; + MyGUI::Button* mDeleteButton; + + MyGUI::Button* mRangeButton; + + MyGUI::Widget* mDurationBox; + MyGUI::Widget* mMagnitudeBox; + MyGUI::Widget* mAreaBox; + + MyGUI::TextBox* mMagnitudeMinValue; + MyGUI::TextBox* mMagnitudeMaxValue; + MyGUI::TextBox* mDurationValue; + MyGUI::TextBox* mAreaValue; + + MyGUI::ScrollBar* mMagnitudeMinSlider; + MyGUI::ScrollBar* mMagnitudeMaxSlider; + MyGUI::ScrollBar* mDurationSlider; + MyGUI::ScrollBar* mAreaSlider; + + MyGUI::TextBox* mAreaText; + + MyGUI::ImageBox* mEffectImage; + MyGUI::TextBox* mEffectName; + + bool mEditing; + + protected: + void onRangeButtonClicked(MyGUI::Widget* sender); + void onDeleteButtonClicked(MyGUI::Widget* sender); + void onOkButtonClicked(MyGUI::Widget* sender); + void onCancelButtonClicked(MyGUI::Widget* sender); + + void onMagnitudeMinChanged(MyGUI::ScrollBar* sender, size_t pos); + void onMagnitudeMaxChanged(MyGUI::ScrollBar* sender, size_t pos); + void onDurationChanged(MyGUI::ScrollBar* sender, size_t pos); + void onAreaChanged(MyGUI::ScrollBar* sender, size_t pos); + void setMagicEffect(const ESM::MagicEffect* effect); + + void updateBoxes(); + + protected: + ESM::ENAMstruct mEffect; + ESM::ENAMstruct mOldEffect; + + const ESM::MagicEffect* mMagicEffect; + + bool mConstantEffect; + }; + + class EffectEditorBase + { + public: + enum Type + { + Spellmaking, + Enchanting + }; + + EffectEditorBase(Type type); + virtual ~EffectEditorBase(); + + void setConstantEffect(bool constant); + + protected: + std::map mButtonMapping; // maps button ID to effect ID + + Gui::MWList* mAvailableEffectsList; + MyGUI::ScrollView* mUsedEffectsView; + + EditEffectDialog mAddEffectDialog; + std::unique_ptr mSelectAttributeDialog; + std::unique_ptr mSelectSkillDialog; + + int mSelectedEffect; + short mSelectedKnownEffectId; + + bool mConstantEffect; + + std::vector mEffects; + + void onEffectAdded(ESM::ENAMstruct effect); + void onEffectModified(ESM::ENAMstruct effect); + void onEffectRemoved(ESM::ENAMstruct effect); + + void onAvailableEffectClicked(MyGUI::Widget* sender); + + void onAttributeOrSkillCancel(); + void onSelectAttribute(); + void onSelectSkill(); + + void onEditEffect(MyGUI::Widget* sender); + + void updateEffectsView(); + + void startEditing(); + void setWidgets(Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView); + + virtual void notifyEffectsChanged() {} + + private: + Type mType; + }; + + class SpellCreationDialog : public WindowBase, public ReferenceInterface, public EffectEditorBase + { + public: + SpellCreationDialog(); + + void onOpen() override; + void clear() override { resetReference(); } + + void onFrame(float dt) override { checkReferenceAvailable(); } + + void setPtr(const MWWorld::Ptr& actor) override; + + std::string_view getWindowIdForLua() const override { return "SpellCreationDialog"; } + + protected: + void onReferenceUnavailable() override; + + void onCancelButtonClicked(MyGUI::Widget* sender); + void onBuyButtonClicked(MyGUI::Widget* sender); + void onAccept(MyGUI::EditBox* sender); + + void notifyEffectsChanged() override; + + MyGUI::EditBox* mNameEdit; + MyGUI::TextBox* mMagickaCost; + MyGUI::TextBox* mSuccessChance; + MyGUI::Button* mBuyButton; + MyGUI::Button* mCancelButton; + MyGUI::TextBox* mPriceLabel; + MyGUI::TextBox* mPlayerGold; + + ESM::Spell mSpell; + }; + +} + +#endif From 937c020e58cda2c23f759b76c22003d5b7b75c1b Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 4 Apr 2025 05:10:26 +0000 Subject: [PATCH 07/55] Update file openmw_spellcreation_dialog.layout --- .../mygui/openmw_spellcreation_dialog.layout | 196 +++++++++--------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/files/data/mygui/openmw_spellcreation_dialog.layout b/files/data/mygui/openmw_spellcreation_dialog.layout index fafe4635e7..ddaff5f14d 100644 --- a/files/data/mygui/openmw_spellcreation_dialog.layout +++ b/files/data/mygui/openmw_spellcreation_dialog.layout @@ -1,98 +1,98 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 284be88b95c687f989c4a88fbb1880a634bc3a8e Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 4 Apr 2025 19:40:55 +0000 Subject: [PATCH 08/55] Update file openmw_spellcreation_dialog.layout --- .../data/mygui/openmw_spellcreation_dialog.layout | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/files/data/mygui/openmw_spellcreation_dialog.layout b/files/data/mygui/openmw_spellcreation_dialog.layout index ddaff5f14d..9ab895833a 100644 --- a/files/data/mygui/openmw_spellcreation_dialog.layout +++ b/files/data/mygui/openmw_spellcreation_dialog.layout @@ -42,11 +42,6 @@ - - - - - @@ -73,6 +68,15 @@ + + + + + + + + + @@ -84,7 +88,6 @@ - From 57bb6f2e2f336a53f4339b7c886474b049a2efbc Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 4 Apr 2025 19:43:31 +0000 Subject: [PATCH 09/55] Update file spellcreationdialog.cpp --- apps/openmw/mwgui/spellcreationdialog.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 651d3014c1..cfa6cb01e7 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -30,6 +30,7 @@ namespace { + bool sortMagicEffects(short id1, short id2) { const MWWorld::Store& gmst @@ -372,7 +373,7 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); + mPlayerGold->setCaptionWithReplacing(MyGUI::utility::toString(playerGold)); startEditing(); } From 30dae411d02a00278f1cd5fc3bb627f280258f54 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 4 Apr 2025 21:13:50 +0000 Subject: [PATCH 10/55] Update file spellcreationdialog.cpp --- apps/openmw/mwgui/spellcreationdialog.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index cfa6cb01e7..c9556a0e2c 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -30,7 +30,6 @@ namespace { - bool sortMagicEffects(short id1, short id2) { const MWWorld::Store& gmst From 57f8355bacb6ef8554b048b1099e00d5803489fa Mon Sep 17 00:00:00 2001 From: Garrett Date: Sat, 5 Apr 2025 02:36:51 +0000 Subject: [PATCH 11/55] Update file enchantingdialog.hpp --- apps/openmw/mwgui/enchantingdialog.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwgui/enchantingdialog.hpp b/apps/openmw/mwgui/enchantingdialog.hpp index 4c720a11fc..616d8cb89c 100644 --- a/apps/openmw/mwgui/enchantingdialog.hpp +++ b/apps/openmw/mwgui/enchantingdialog.hpp @@ -70,6 +70,7 @@ namespace MWGui MyGUI::TextBox* mSuccessChance; MyGUI::TextBox* mPrice; MyGUI::TextBox* mPriceText; + MyGUI::TextBox* mPlayerGold; MWMechanics::Enchanting mEnchanting; ESM::EffectList mEffectList; From 7f11579d394a335c568e217c0860c771db013c63 Mon Sep 17 00:00:00 2001 From: Garrett Date: Sat, 5 Apr 2025 02:37:31 +0000 Subject: [PATCH 12/55] Update file enchantingdialog.cpp --- apps/openmw/mwgui/enchantingdialog.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index af4a3e8ce3..acd9764e3b 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -50,7 +50,7 @@ namespace MWGui getWidget(mBuyButton, "BuyButton"); getWidget(mPrice, "PriceLabel"); getWidget(mPriceText, "PriceTextLabel"); - + getWidget(mPlayerGold, "PlayerGold"); setWidgets(mAvailableEffectsList, mUsedEffectsView); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onCancelButtonClicked); @@ -65,6 +65,10 @@ namespace MWGui { center(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mName); + + MWWorld::Ptr player = MWMechanics::getPlayer(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + mPlayerGold->setCaptionWithReplacing(MyGUI::utility::toString(playerGold)); } void EnchantingDialog::setSoulGem(const MWWorld::Ptr& gem) From fce73395e5e0c26a75be47fe87022819582010d8 Mon Sep 17 00:00:00 2001 From: Garrett Date: Sat, 5 Apr 2025 02:39:19 +0000 Subject: [PATCH 13/55] Update file openmw_enchanting_dialog.layout --- files/data/mygui/openmw_enchanting_dialog.layout | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/files/data/mygui/openmw_enchanting_dialog.layout b/files/data/mygui/openmw_enchanting_dialog.layout index c0e3ba0d21..839de8d399 100644 --- a/files/data/mygui/openmw_enchanting_dialog.layout +++ b/files/data/mygui/openmw_enchanting_dialog.layout @@ -138,6 +138,14 @@ + + + + + + + + From 30e5e17e4c6df58dd24aab97c2b48383f7f470c9 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 4 Jul 2025 13:46:08 -0500 Subject: [PATCH 14/55] Remove enchanting dialog, missing empty lines --- apps/openmw/mwgui/enchantingdialog.cpp | 6 +----- apps/openmw/mwgui/enchantingdialog.hpp | 1 - apps/openmw/mwgui/spellcreationdialog.cpp | 1 + 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index acd9764e3b..af4a3e8ce3 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -50,7 +50,7 @@ namespace MWGui getWidget(mBuyButton, "BuyButton"); getWidget(mPrice, "PriceLabel"); getWidget(mPriceText, "PriceTextLabel"); - getWidget(mPlayerGold, "PlayerGold"); + setWidgets(mAvailableEffectsList, mUsedEffectsView); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onCancelButtonClicked); @@ -65,10 +65,6 @@ namespace MWGui { center(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mName); - - MWWorld::Ptr player = MWMechanics::getPlayer(); - int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - mPlayerGold->setCaptionWithReplacing(MyGUI::utility::toString(playerGold)); } void EnchantingDialog::setSoulGem(const MWWorld::Ptr& gem) diff --git a/apps/openmw/mwgui/enchantingdialog.hpp b/apps/openmw/mwgui/enchantingdialog.hpp index 616d8cb89c..4c720a11fc 100644 --- a/apps/openmw/mwgui/enchantingdialog.hpp +++ b/apps/openmw/mwgui/enchantingdialog.hpp @@ -70,7 +70,6 @@ namespace MWGui MyGUI::TextBox* mSuccessChance; MyGUI::TextBox* mPrice; MyGUI::TextBox* mPriceText; - MyGUI::TextBox* mPlayerGold; MWMechanics::Enchanting mEnchanting; ESM::EffectList mEffectList; diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index c9556a0e2c..cfa6cb01e7 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -30,6 +30,7 @@ namespace { + bool sortMagicEffects(short id1, short id2) { const MWWorld::Store& gmst From b3105e93826c79ba9746b71c94cdbf407bda3a3a Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 4 Jul 2025 13:49:32 -0500 Subject: [PATCH 15/55] Fix other changes --- files/data/mygui/openmw_enchanting_dialog.layout | 8 -------- files/data/mygui/openmw_spellcreation_dialog.layout | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/files/data/mygui/openmw_enchanting_dialog.layout b/files/data/mygui/openmw_enchanting_dialog.layout index 839de8d399..c0e3ba0d21 100644 --- a/files/data/mygui/openmw_enchanting_dialog.layout +++ b/files/data/mygui/openmw_enchanting_dialog.layout @@ -138,14 +138,6 @@ - - - - - - - - diff --git a/files/data/mygui/openmw_spellcreation_dialog.layout b/files/data/mygui/openmw_spellcreation_dialog.layout index 9ab895833a..2da1eb621d 100644 --- a/files/data/mygui/openmw_spellcreation_dialog.layout +++ b/files/data/mygui/openmw_spellcreation_dialog.layout @@ -72,9 +72,8 @@ - - + @@ -88,6 +87,7 @@ + From ed8b9742ae7e06cfd5bad3292888263e6c579c56 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 4 Jul 2025 13:52:22 -0500 Subject: [PATCH 16/55] Add name --- files/data/mygui/openmw_spellcreation_dialog.layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/mygui/openmw_spellcreation_dialog.layout b/files/data/mygui/openmw_spellcreation_dialog.layout index 2da1eb621d..d241c63350 100644 --- a/files/data/mygui/openmw_spellcreation_dialog.layout +++ b/files/data/mygui/openmw_spellcreation_dialog.layout @@ -68,7 +68,7 @@ - + From 424a62187abcd74f96164ec7a305c3b98e7ea536 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 4 Jul 2025 14:26:16 -0500 Subject: [PATCH 17/55] whitespace? --- apps/openmw/mwgui/spellcreationdialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index cfa6cb01e7..6bd9ef3ac8 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -30,7 +30,7 @@ namespace { - + bool sortMagicEffects(short id1, short id2) { const MWWorld::Store& gmst From 3504e85051a6bf7ced2db4abc232e8afdbe78126 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 4 Jul 2025 14:33:15 -0500 Subject: [PATCH 18/55] More whitespace --- files/data/mygui/openmw_spellcreation_dialog.layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/mygui/openmw_spellcreation_dialog.layout b/files/data/mygui/openmw_spellcreation_dialog.layout index d241c63350..4dc895d26a 100644 --- a/files/data/mygui/openmw_spellcreation_dialog.layout +++ b/files/data/mygui/openmw_spellcreation_dialog.layout @@ -41,7 +41,7 @@ - + From c541cb96cccbee20db17d2b638c261d1904afa86 Mon Sep 17 00:00:00 2001 From: epochwon Date: Fri, 2 May 2025 18:08:08 -0400 Subject: [PATCH 19/55] doppler take 2 --- apps/launcher/settingspage.cpp | 3 ++ apps/launcher/ui/settingspage.ui | 44 +++++++++++++++++++++++ apps/openmw/mwbase/soundmanager.hpp | 2 ++ apps/openmw/mwsound/openaloutput.cpp | 24 ++++++++----- apps/openmw/mwsound/openaloutput.hpp | 7 ++-- apps/openmw/mwsound/sound.hpp | 6 ++++ apps/openmw/mwsound/soundmanagerimp.cpp | 23 +++++++++++- apps/openmw/mwsound/soundmanagerimp.hpp | 3 ++ apps/openmw/mwsound/soundoutput.hpp | 2 +- apps/openmw/mwworld/projectilemanager.cpp | 5 +++ apps/openmw/mwworld/worldimp.cpp | 2 ++ components/settings/categories/sound.hpp | 1 + files/settings-default.cfg | 3 ++ 13 files changed, 111 insertions(+), 14 deletions(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index dfddc45bc5..f7000ab5dd 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -293,6 +293,7 @@ bool Launcher::SettingsPage::loadSettings() } } loadSettingBool(Settings::sound().mCameraListener, *cameraListenerCheckBox); + dopplerSpinBox->setValue(Settings::sound().mDopplerFactor); } // Interface Changes @@ -486,6 +487,8 @@ void Launcher::SettingsPage::saveSettings() const bool cCameraListener = cameraListenerCheckBox->checkState() != Qt::Unchecked; Settings::sound().mCameraListener.set(cCameraListener); + + Settings::sound().mDopplerFactor.set(dopplerSpinBox->value()); } // Interface Changes diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index e792ac2843..c383a6abbf 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -1235,6 +1235,50 @@ + + + + + Controls the strength of the doppler effect. Zero means it is completely disabled. + + + Doppler Factor + + + + + + + + 0 + 0 + + + + + 283 + 0 + + + + 2 + + + 0.000000000000000 + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.250000000000000 + + + + + Qt::Vertical diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 532bc771ba..5591b7205c 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -234,6 +234,8 @@ namespace MWBase const osg::Vec3f& pos, const osg::Vec3f& dir, const osg::Vec3f& up, bool underwater) = 0; + virtual void setListenerVel(const osg::Vec3f& vel) = 0; + virtual void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) = 0; void setSimulationTimeScale(float scale) { mSimulationTimeScale = scale; } diff --git a/apps/openmw/mwsound/openaloutput.cpp b/apps/openmw/mwsound/openaloutput.cpp index 0f27524912..a8bc166c94 100644 --- a/apps/openmw/mwsound/openaloutput.cpp +++ b/apps/openmw/mwsound/openaloutput.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "efxpresets.h" @@ -1137,12 +1138,13 @@ namespace MWSound alSourcef(source, AL_GAIN, gain); alSourcef(source, AL_PITCH, pitch); + alSourcef(source, AL_DOPPLER_FACTOR, 0.0f); alSourcefv(source, AL_POSITION, pos.ptr()); alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); } - void OpenALOutput::initCommon3D(ALuint source, const osg::Vec3f& pos, ALfloat mindist, ALfloat maxdist, + void OpenALOutput::initCommon3D(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat mindist, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv) { alSourcef(source, AL_REFERENCE_DISTANCE, mindist); @@ -1177,13 +1179,14 @@ namespace MWSound alSourcef(source, AL_GAIN, gain); alSourcef(source, AL_PITCH, pitch); + alSourcef(source, AL_DOPPLER_FACTOR, Settings::sound().mDopplerFactor); alSourcefv(source, AL_POSITION, pos.ptr()); alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alSourcefv(source, AL_VELOCITY, vel.ptr()); } void OpenALOutput::updateCommon( - ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv) + ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv) { if (useenv && mListenerEnv == Env_Underwater && !mWaterFilter) { @@ -1195,7 +1198,7 @@ namespace MWSound alSourcef(source, AL_PITCH, pitch); alSourcefv(source, AL_POSITION, pos.ptr()); alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alSourcefv(source, AL_VELOCITY, vel.ptr()); } bool OpenALOutput::playSound(Sound* sound, Sound_Handle data, float offset) @@ -1248,7 +1251,7 @@ namespace MWSound } source = mFreeSources.front(); - initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), + initCommon3D(source, sound->getPosition(), sound->getVelocity(), sound->getMinDistance(), sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), sound->getIsLooping(), sound->getUseEnv()); alSourcei(source, AL_BUFFER, GET_PTRID(data)); alSourcef(source, AL_SEC_OFFSET, offset); @@ -1312,7 +1315,7 @@ namespace MWSound return; ALuint source = GET_PTRID(sound->mHandle); - updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), + updateCommon(source, sound->getPosition(), sound->getVelocity(), sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), sound->getUseEnv()); getALError(); } @@ -1360,7 +1363,7 @@ namespace MWSound if (sound->getIsLooping()) Log(Debug::Warning) << "Warning: cannot loop stream \"" << decoder->getName() << "\""; - initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), + initCommon3D(source, sound->getPosition(), sound->getVelocity(), sound->getMinDistance(), sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), false, sound->getUseEnv()); if (getALError() != AL_NO_ERROR) return false; @@ -1443,7 +1446,7 @@ namespace MWSound OpenAL_SoundStream* stream = reinterpret_cast(sound->mHandle); ALuint source = stream->mSource; - updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), + updateCommon(source, sound->getPosition(), sound->getVelocity(), sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), sound->getUseEnv()); getALError(); } @@ -1459,12 +1462,13 @@ namespace MWSound } void OpenALOutput::updateListener( - const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, Environment env) + const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, const osg::Vec3f& vel, Environment env) { if (mContext) { ALfloat orient[6] = { atdir.x(), atdir.y(), atdir.z(), updir.x(), updir.y(), updir.z() }; alListenerfv(AL_POSITION, pos.ptr()); + alListenerfv(AL_VELOCITY, vel.ptr()); alListenerfv(AL_ORIENTATION, orient); if (env != mListenerEnv) @@ -1497,6 +1501,7 @@ namespace MWSound } mListenerPos = pos; + mListenerVel = vel; mListenerEnv = env; } @@ -1583,6 +1588,7 @@ namespace MWSound , mDevice(nullptr) , mContext(nullptr) , mListenerPos(0.0f, 0.0f, 0.0f) + , mListenerVel(0.0f, 0.0f, 0.0f) , mListenerEnv(Env_Normal) , mWaterFilter(0) , mWaterEffect(0) diff --git a/apps/openmw/mwsound/openaloutput.hpp b/apps/openmw/mwsound/openaloutput.hpp index 4e96dd1627..e7089388c3 100644 --- a/apps/openmw/mwsound/openaloutput.hpp +++ b/apps/openmw/mwsound/openaloutput.hpp @@ -46,6 +46,7 @@ namespace MWSound StreamVec mActiveStreams; osg::Vec3f mListenerPos; + osg::Vec3f mListenerVel; Environment mListenerEnv; ALuint mWaterFilter; @@ -64,11 +65,11 @@ namespace MWSound std::unique_ptr mDefaultDeviceThread; void initCommon2D(ALuint source, const osg::Vec3f& pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv); - void initCommon3D(ALuint source, const osg::Vec3f& pos, ALfloat mindist, ALfloat maxdist, ALfloat gain, + void initCommon3D(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat mindist, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv); void updateCommon( - ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv); + ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv); float getTimeScaledPitch(SoundBase* sound); @@ -109,7 +110,7 @@ namespace MWSound void finishUpdate() override; void updateListener( - const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, Environment env) override; + const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, const osg::Vec3f& vel, Environment env) override; void pauseSounds(int types) override; void resumeSounds(int types) override; diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 5160b2934f..0ac30d0ccd 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -31,6 +31,8 @@ namespace MWSound struct SoundParams { osg::Vec3f mPos; + osg::Vec3f mLastPos; + osg::Vec3f mVel; float mVolume = 1.0f; float mBaseVolume = 1.0f; float mPitch = 1.0f; @@ -57,6 +59,8 @@ namespace MWSound public: void setPosition(const osg::Vec3f& pos) { mParams.mPos = pos; } + void setLastPosition(const osg::Vec3f& lastpos) { mParams.mLastPos = lastpos; } + void setVelocity(const osg::Vec3f& vel) { mParams.mVel = vel; } void setVolume(float volume) { mParams.mVolume = volume; } void setBaseVolume(float volume) { mParams.mBaseVolume = volume; } void setFadeout(float duration) { setFade(duration, 0.0, Play_StopAtFadeEnd); } @@ -150,6 +154,8 @@ namespace MWSound } const osg::Vec3f& getPosition() const { return mParams.mPos; } + const osg::Vec3f& getLastPosition() const { return mParams.mLastPos; } + const osg::Vec3f& getVelocity() const {return mParams.mVel; } float getRealVolume() const { return mParams.mVolume * mParams.mBaseVolume * mParams.mFadeVolume; } float getPitch() const { return mParams.mPitch; } float getMinDistance() const { return mParams.mMinDistance; } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 66bdfbdbfa..f7dff94d1c 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -119,6 +120,7 @@ namespace MWSound , mListenerPos(0, 0, 0) , mListenerDir(1, 0, 0) , mListenerUp(0, 0, 1) + , mListenerVel(0, 0, 0) , mUnderwaterSound(nullptr) , mNearWaterSound(nullptr) , mPlaybackPaused(false) @@ -959,8 +961,16 @@ namespace MWSound mUnderwaterSound = nullptr; } + float physicsFramerate = 60.f; + if (const char* env = getenv("OPENMW_PHYSICS_FPS")) + { + if (const auto physFramerate = Misc::StringUtils::toNumeric(env); + physFramerate.has_value() && *physFramerate > 0) + physicsFramerate = *physFramerate; + } + mOutput->startUpdate(); - mOutput->updateListener(mListenerPos, mListenerDir, mListenerUp, env); + mOutput->updateListener(mListenerPos, mListenerDir, mListenerUp, mListenerVel, env); updateMusic(duration); @@ -977,7 +987,11 @@ namespace MWSound if (sound->getIs3D()) { if (!ptr.isEmpty()) + { + sound->setLastPosition(sound->getPosition()); sound->setPosition(ptr.getRefData().getPosition().asVec3()); + sound->setVelocity((sound->getPosition() - sound->getLastPosition()) * physicsFramerate); + } cull3DSound(sound); } @@ -1013,8 +1027,10 @@ namespace MWSound { if (!ptr.isEmpty()) { + sound->setLastPosition(sound->getPosition()); MWBase::World* world = MWBase::Environment::get().getWorld(); sound->setPosition(world->getActorHeadTransform(ptr).getTrans()); + sound->setVelocity((sound->getPosition() - sound->getLastPosition()) * physicsFramerate); } cull3DSound(sound); @@ -1153,6 +1169,11 @@ namespace MWSound mWaterSoundUpdater.setUnderwater(underwater); } + void SoundManager::setListenerVel(const osg::Vec3f& vel) + { + mListenerVel = vel; + } + void SoundManager::updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) { SoundMap::iterator snditer = mActiveSounds.find(old.mRef); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 8fc7e6701f..f1b79557a0 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -92,6 +92,7 @@ namespace MWSound osg::Vec3f mListenerPos; osg::Vec3f mListenerDir; osg::Vec3f mListenerUp; + osg::Vec3f mListenerVel; int mPausedSoundTypes[BlockerType::MaxCount] = {}; @@ -283,6 +284,8 @@ namespace MWSound void setListenerPosDir( const osg::Vec3f& pos, const osg::Vec3f& dir, const osg::Vec3f& up, bool underwater) override; + void setListenerVel(const osg::Vec3f& vel) override; + void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) override; void clear() override; diff --git a/apps/openmw/mwsound/soundoutput.hpp b/apps/openmw/mwsound/soundoutput.hpp index e5c266b204..d0024e9a7c 100644 --- a/apps/openmw/mwsound/soundoutput.hpp +++ b/apps/openmw/mwsound/soundoutput.hpp @@ -62,7 +62,7 @@ namespace MWSound virtual void finishUpdate() = 0; virtual void updateListener( - const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, Environment env) + const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, const osg::Vec3f& vel, Environment env) = 0; virtual void pauseSounds(int types) = 0; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 6ea510e1e2..c436f86df5 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -461,6 +461,11 @@ namespace MWWorld update(magicBoltState, duration); + for (const auto& sound : magicBoltState.mSounds) + { + sound->setVelocity(direction * speed); + } + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit // result. std::vector targetActors; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f608b8c781..cf9a0d7b07 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1467,6 +1467,8 @@ namespace MWWorld void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity) { mPhysics->queueObjectMovement(ptr, velocity); + if(ptr == MWMechanics::getPlayer()) + MWBase::Environment::get().getSoundManager()->setListenerVel(velocity); } void World::updateAnimatedCollisionShape(const Ptr& ptr) diff --git a/components/settings/categories/sound.hpp b/components/settings/categories/sound.hpp index 8398a38c55..99e70fcdb2 100644 --- a/components/settings/categories/sound.hpp +++ b/components/settings/categories/sound.hpp @@ -24,6 +24,7 @@ namespace Settings SettingValue mHrtfEnable{ mIndex, "Sound", "hrtf enable" }; SettingValue mHrtf{ mIndex, "Sound", "hrtf" }; SettingValue mCameraListener{ mIndex, "Sound", "camera listener" }; + SettingValue mDopplerFactor{ mIndex, "Sound", "doppler factor", makeClampSanitizerFloat(0, 1) }; }; } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index e5654794cc..33c5c60ce5 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -627,6 +627,9 @@ hrtf = # Specifies whether to use camera as audio listener camera listener = false +# Specifies strength of doppler effect +doppler factor = 0.25 + [Video] # Resolution of the OpenMW window or screen. From d29c01be26dc1b86e637b2888a51b9fb6ba29e1a Mon Sep 17 00:00:00 2001 From: epochwon Date: Fri, 2 May 2025 21:41:37 -0400 Subject: [PATCH 20/55] appease clang + docs --- apps/openmw/mwsound/openaloutput.cpp | 16 ++++++++-------- apps/openmw/mwsound/openaloutput.hpp | 12 ++++++------ apps/openmw/mwsound/sound.hpp | 2 +- apps/openmw/mwsound/soundoutput.hpp | 4 ++-- apps/openmw/mwworld/worldimp.cpp | 2 +- docs/source/reference/modding/settings/sound.rst | 10 ++++++++++ 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwsound/openaloutput.cpp b/apps/openmw/mwsound/openaloutput.cpp index a8bc166c94..a586fc611a 100644 --- a/apps/openmw/mwsound/openaloutput.cpp +++ b/apps/openmw/mwsound/openaloutput.cpp @@ -1185,8 +1185,8 @@ namespace MWSound alSourcefv(source, AL_VELOCITY, vel.ptr()); } - void OpenALOutput::updateCommon( - ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv) + void OpenALOutput::updateCommon(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat maxdist, + ALfloat gain, ALfloat pitch, bool useenv) { if (useenv && mListenerEnv == Env_Underwater && !mWaterFilter) { @@ -1315,8 +1315,8 @@ namespace MWSound return; ALuint source = GET_PTRID(sound->mHandle); - updateCommon(source, sound->getPosition(), sound->getVelocity(), sound->getMaxDistance(), sound->getRealVolume(), - getTimeScaledPitch(sound), sound->getUseEnv()); + updateCommon(source, sound->getPosition(), sound->getVelocity(), sound->getMaxDistance(), + sound->getRealVolume(), getTimeScaledPitch(sound), sound->getUseEnv()); getALError(); } @@ -1363,8 +1363,8 @@ namespace MWSound if (sound->getIsLooping()) Log(Debug::Warning) << "Warning: cannot loop stream \"" << decoder->getName() << "\""; - initCommon3D(source, sound->getPosition(), sound->getVelocity(), sound->getMinDistance(), sound->getMaxDistance(), - sound->getRealVolume(), getTimeScaledPitch(sound), false, sound->getUseEnv()); + initCommon3D(source, sound->getPosition(), sound->getVelocity(), sound->getMinDistance(), + sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), false, sound->getUseEnv()); if (getALError() != AL_NO_ERROR) return false; @@ -1446,8 +1446,8 @@ namespace MWSound OpenAL_SoundStream* stream = reinterpret_cast(sound->mHandle); ALuint source = stream->mSource; - updateCommon(source, sound->getPosition(), sound->getVelocity(), sound->getMaxDistance(), sound->getRealVolume(), - getTimeScaledPitch(sound), sound->getUseEnv()); + updateCommon(source, sound->getPosition(), sound->getVelocity(), sound->getMaxDistance(), + sound->getRealVolume(), getTimeScaledPitch(sound), sound->getUseEnv()); getALError(); } diff --git a/apps/openmw/mwsound/openaloutput.hpp b/apps/openmw/mwsound/openaloutput.hpp index e7089388c3..d689d08546 100644 --- a/apps/openmw/mwsound/openaloutput.hpp +++ b/apps/openmw/mwsound/openaloutput.hpp @@ -65,11 +65,11 @@ namespace MWSound std::unique_ptr mDefaultDeviceThread; void initCommon2D(ALuint source, const osg::Vec3f& pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv); - void initCommon3D(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat mindist, ALfloat maxdist, ALfloat gain, - ALfloat pitch, bool loop, bool useenv); + void initCommon3D(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat mindist, ALfloat maxdist, + ALfloat gain, ALfloat pitch, bool loop, bool useenv); - void updateCommon( - ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv); + void updateCommon(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat maxdist, ALfloat gain, + ALfloat pitch, bool useenv); float getTimeScaledPitch(SoundBase* sound); @@ -109,8 +109,8 @@ namespace MWSound void startUpdate() override; void finishUpdate() override; - void updateListener( - const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, const osg::Vec3f& vel, Environment env) override; + void updateListener(const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, + const osg::Vec3f& vel, Environment env) override; void pauseSounds(int types) override; void resumeSounds(int types) override; diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 0ac30d0ccd..7d020c31f9 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -155,7 +155,7 @@ namespace MWSound const osg::Vec3f& getPosition() const { return mParams.mPos; } const osg::Vec3f& getLastPosition() const { return mParams.mLastPos; } - const osg::Vec3f& getVelocity() const {return mParams.mVel; } + const osg::Vec3f& getVelocity() const { return mParams.mVel; } float getRealVolume() const { return mParams.mVolume * mParams.mBaseVolume * mParams.mFadeVolume; } float getPitch() const { return mParams.mPitch; } float getMinDistance() const { return mParams.mMinDistance; } diff --git a/apps/openmw/mwsound/soundoutput.hpp b/apps/openmw/mwsound/soundoutput.hpp index d0024e9a7c..4b24f159d3 100644 --- a/apps/openmw/mwsound/soundoutput.hpp +++ b/apps/openmw/mwsound/soundoutput.hpp @@ -61,8 +61,8 @@ namespace MWSound virtual void startUpdate() = 0; virtual void finishUpdate() = 0; - virtual void updateListener( - const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, const osg::Vec3f& vel, Environment env) + virtual void updateListener(const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, + const osg::Vec3f& vel, Environment env) = 0; virtual void pauseSounds(int types) = 0; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index cf9a0d7b07..eaa8799e84 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1467,7 +1467,7 @@ namespace MWWorld void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity) { mPhysics->queueObjectMovement(ptr, velocity); - if(ptr == MWMechanics::getPlayer()) + if (ptr == MWMechanics::getPlayer()) MWBase::Environment::get().getSoundManager()->setListenerVel(velocity); } diff --git a/docs/source/reference/modding/settings/sound.rst b/docs/source/reference/modding/settings/sound.rst index e38cd9de78..b462428ad1 100644 --- a/docs/source/reference/modding/settings/sound.rst +++ b/docs/source/reference/modding/settings/sound.rst @@ -121,3 +121,13 @@ Sound Settings When true, uses the camera position and direction for audio instead of the player position. This makes audio in third person sound relative to camera instead of the player. False is vanilla Morrowind behaviour. + +.. omw-setting:: + :title: doppler factor + :type: float32 + :range: 0.0 (disabled), 1.0 (maximum strength) + :default: 0.25 + :location: :bdg-success:`Launcher > Settings > Audio` + + This setting controls the strength of the doppler effect. Doppler effect increases or decreases the pitch of sounds + relative to the velocity of the source and the listener. \ No newline at end of file From 120668849416db221e0cae538bc40185a6205b13 Mon Sep 17 00:00:00 2001 From: epochwon Date: Sat, 3 May 2025 14:41:53 -0400 Subject: [PATCH 21/55] language files maybe? --- files/lang/launcher_de.ts | 160 +---------------------------------- files/lang/launcher_en.ts | 172 ++------------------------------------ files/lang/launcher_fr.ts | 90 +++++++++++--------- files/lang/launcher_ru.ts | 90 +++++++++++--------- files/lang/launcher_sv.ts | 90 +++++++++++--------- 5 files changed, 157 insertions(+), 445 deletions(-) diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 86773e5a54..bb53c32ce2 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -876,50 +876,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Select your preferred HRTF profile. - - Interface - - - - <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - - - - <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - - - - <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - - - - Size of characters in game texts. - - - - <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - - <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - - <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - - - - <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - - - - <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - - - - <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - - <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> @@ -928,42 +888,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - - Miscellaneous - - - - Saves - - - - JPG - - - - PNG - - - - TGA - - - - Testing - - - - These settings are intended for testing mods and will cause issues if used for normal gameplay. - - - - <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - - - - Browse… - - Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. @@ -1052,18 +976,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Lighting - - Tooltip - - - - Crosshair - - - - Screenshots - - <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> @@ -1319,62 +1231,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov HRTF Profile - - Tooltip and Crosshair - - - - GUI Scaling Factor - - - - Show Effect Duration - - - - Change Dialogue Topic Color - - - - Font Size - - - - Show Projectile Damage - - - - Show Melee Info - - - - Stretch Menu Background - - - - Show Owned Objects - - - - Show Enchant Chance - - - - Maximum Quicksaves - - - - Screenshot Format - - - - Grab Cursor - - - - Default Cell - - Bounds @@ -1428,23 +1284,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Can Zoom on Maps + Controls the strength of the doppler effect. Zero means it is completely disabled. - Notify on Saved Screenshot - - - - Skip Menu and Generate Default Character - - - - Start Default Character at - - - - Run Script After Startup: + Doppler Factor diff --git a/files/lang/launcher_en.ts b/files/lang/launcher_en.ts index a0319318e8..5d9fd74b33 100644 --- a/files/lang/launcher_en.ts +++ b/files/lang/launcher_en.ts @@ -1275,170 +1275,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Use the Camera as the Sound Listener - - Interface - - - - Tooltip - - - - Crosshair - - - - Tooltip and Crosshair - - - - <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - - - - GUI Scaling Factor - - - - <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - - - - Show Effect Duration - - - - <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - - - - Change Dialogue Topic Color - - - - Size of characters in game texts. - - - - Font Size - - - - <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - - - - Can Zoom on Maps - - - - <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - - - - Show Projectile Damage - - - - <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - - - - Show Melee Info - - - - <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - - - - Stretch Menu Background - - - - Show Owned Objects - - - - <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - - - - Show Enchant Chance - - - - Miscellaneous - - - - Saves - - - - Maximum Quicksaves - - - - Screenshots - - - - Screenshot Format - - - - JPG - - - - PNG - - - - TGA - - - - Notify on Saved Screenshot - - - - Testing - - - - These settings are intended for testing mods and will cause issues if used for normal gameplay. - - - - <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - - - - Grab Cursor - - - - Skip Menu and Generate Default Character - - - - Start Default Character at - - - - Default Cell - - - - Run Script After Startup: - - - - Browse… - - Smooth Animation Transitions @@ -1447,5 +1283,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> + + Controls the strength of the doppler effect. Zero means it is completely disabled. + + + + Doppler Factor + + diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 569a460cd0..546e9d3f18 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -878,27 +878,27 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Interface - Interface + Interface <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - <html><body><p>Cette option agrandit ou diminue l'interface utilisateur. Une valeur de 1 correspond à sa taille nominale.</p></body></html> + <html><body><p>Cette option agrandit ou diminue l'interface utilisateur. Une valeur de 1 correspond à sa taille nominale.</p></body></html> <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - <html><body><p>Cette option permet d'activer la durée de vie restante des effets magiques et des lampes. Cette durée restante s'affiche en passant la souris au-dessus de l'icône de l'effet magique.</p></body></html> + <html><body><p>Cette option permet d'activer la durée de vie restante des effets magiques et des lampes. Cette durée restante s'affiche en passant la souris au-dessus de l'icône de l'effet magique.</p></body></html> <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - <html><body><p>Lorsque cette option est activée, les sujets de conversation de la fenêtre de dialogue auront des couleurs différentes si ceux-ci sont spécifiques au PNJ ou qu'ils ont déjà été lus autre part. Ces couleurs peuvent être changées dans le fichier settings.cfg.</p></body></html> + <html><body><p>Lorsque cette option est activée, les sujets de conversation de la fenêtre de dialogue auront des couleurs différentes si ceux-ci sont spécifiques au PNJ ou qu'ils ont déjà été lus autre part. Ces couleurs peuvent être changées dans le fichier settings.cfg.</p></body></html> Size of characters in game texts. - <html><body><p>Taille des caractères des textes du jeu.</p></body></html> + <html><body><p>Taille des caractères des textes du jeu.</p></body></html> <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - <html><body><p>Cette option permet de zoomer en jeu sur la carte (carte locale et carte du monde).</p></body></html> + <html><body><p>Cette option permet de zoomer en jeu sur la carte (carte locale et carte du monde).</p></body></html> <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> @@ -906,19 +906,19 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><body><p>Lorsque cette option est activée, les bonus de dommages des flèches et des carreaux apparaissent dans l'infobulle de l'objet.</p></body></html> + <html><body><p>Lorsque cette option est activée, les bonus de dommages des flèches et des carreaux apparaissent dans l'infobulle de l'objet.</p></body></html> <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><body><p>Lorsque cette option est activée, la portée et la rapidité d'attaque des armes de mêlées apparaissent dans l'infobulle de l'objet.</p></body></html> + <html><body><p>Lorsque cette option est activée, la portée et la rapidité d'attaque des armes de mêlées apparaissent dans l'infobulle de l'objet.</p></body></html> <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - <html><body><p>Étends les menus, les écrans de chargement et autres éléments d'interface aux formats d'image du jeu.</p></body></html> + <html><body><p>Étends les menus, les écrans de chargement et autres éléments d'interface aux formats d'image du jeu.</p></body></html> <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - <html><body><p>Lorsque l'option est activée, affiche les chances de succès d'un enchantement dans le menu des enchantements.</body></html> + <html><body><p>Lorsque l'option est activée, affiche les chances de succès d'un enchantement dans le menu des enchantements.</body></html> <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> @@ -930,39 +930,39 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Miscellaneous - Divers + Divers Saves - Sauvegardes + Sauvegardes JPG - JPG + JPG PNG - PNG + PNG TGA - TGA + TGA Testing - Testeurs + Testeurs These settings are intended for testing mods and will cause issues if used for normal gameplay. - Ces options sont destinées aux testeurs de contenus additionnels (mods). Ils poseront problème lors d'une partie normale. + Ces options sont destinées aux testeurs de contenus additionnels (mods). Ils poseront problème lors d'une partie normale. <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - <html><body><p>Lorsque cette option est activée, OpenMW capture le curseur de la souris.</p><p>Lorsque le jeu est en mode "Regard/Déplacement", OpenMW centre le curseur au milieu de l'écran; sans tenir compte de cette option (le curseur/viseur est toujours au centre de la fenêtre d'OpenMW). Néanmoins, lorsqu'un menu est ouvert, cette option permet de récupérer le contrôle de la souris. En effet, lorsque l'option est active, le mouvement du curseur s'arrête aux bords de la fenêtre et empêche d'accéder à d'autres applications. Tandis que, lorsque l'option est désactivée, le curseur peut sortir librement de la fenêtre et accéder au bureau.</p><p>Cette option ne s'applique pas à l'écran lorsque la touche Échap a été enfoncée, dans ce cas, le curseur n'est jamais capturé. Cette option n'agit pas non plus sur "Atl-Tab" ou toute autre séquence de touches utilisée par le système d'exploitation pour récupérer le contrôle du curseur de la souris. </p><p>Cette option interagit avec l'option de minimisation de la fenêtre lors de la perte de focus en modifiant ce qui est considéré comme une perte de focus. Par exemple, avec une configuration à doubles écrans, il peut être plus agréable d'accéder au second écran avec cette option désactivée.</p><p>Note aux développeurs : Il est préférable de désactiver cette option lorsque le jeu tourne dans un débogueur, ceci afin d'empêcher le curseur de la souris de devenir inutilisable lorsque le jeu s'arrête sur un breakpoint.</p></body></html> + <html><body><p>Lorsque cette option est activée, OpenMW capture le curseur de la souris.</p><p>Lorsque le jeu est en mode "Regard/Déplacement", OpenMW centre le curseur au milieu de l'écran; sans tenir compte de cette option (le curseur/viseur est toujours au centre de la fenêtre d'OpenMW). Néanmoins, lorsqu'un menu est ouvert, cette option permet de récupérer le contrôle de la souris. En effet, lorsque l'option est active, le mouvement du curseur s'arrête aux bords de la fenêtre et empêche d'accéder à d'autres applications. Tandis que, lorsque l'option est désactivée, le curseur peut sortir librement de la fenêtre et accéder au bureau.</p><p>Cette option ne s'applique pas à l'écran lorsque la touche Échap a été enfoncée, dans ce cas, le curseur n'est jamais capturé. Cette option n'agit pas non plus sur "Atl-Tab" ou toute autre séquence de touches utilisée par le système d'exploitation pour récupérer le contrôle du curseur de la souris. </p><p>Cette option interagit avec l'option de minimisation de la fenêtre lors de la perte de focus en modifiant ce qui est considéré comme une perte de focus. Par exemple, avec une configuration à doubles écrans, il peut être plus agréable d'accéder au second écran avec cette option désactivée.</p><p>Note aux développeurs : Il est préférable de désactiver cette option lorsque le jeu tourne dans un débogueur, ceci afin d'empêcher le curseur de la souris de devenir inutilisable lorsque le jeu s'arrête sur un breakpoint.</p></body></html> Browse… - Parcourir... + Parcourir... Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. @@ -1054,15 +1054,15 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Tooltip - Infobulles + Infobulles Crosshair - Réticule de visée + Réticule de visée Screenshots - Captures d'écran + Captures d'écran <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> @@ -1324,59 +1324,59 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Tooltip and Crosshair - Infobulles et réticule de visée + Infobulles et réticule de visée GUI Scaling Factor - Échelle de l'interface + Échelle de l'interface Show Effect Duration - Afficher la durée des effets + Afficher la durée des effets Change Dialogue Topic Color - Changer la couleur des sujets de conversation + Changer la couleur des sujets de conversation Font Size - Taille des polices + Taille des polices Show Projectile Damage - Afficher les dommages des projectiles + Afficher les dommages des projectiles Show Melee Info - Afficher les infos de mêlée + Afficher les infos de mêlée Stretch Menu Background - Étendre les arrière-plans + Étendre les arrière-plans Show Owned Objects - Afficher la possession des objets + Afficher la possession des objets Show Enchant Chance - Afficher les chances d'enchantement + Afficher les chances d'enchantement Maximum Quicksaves - Nombre maximum de sauvegardes rapides + Nombre maximum de sauvegardes rapides Screenshot Format - Format des captures + Format des captures Grab Cursor - Capturer le curseur + Capturer le curseur Default Cell - la cellule par défaut + la cellule par défaut Bounds @@ -1432,23 +1432,31 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Can Zoom on Maps - Permettre le zoom sur la carte + Permettre le zoom sur la carte Notify on Saved Screenshot - Notifier l'enregistrement des captures d'écran + Notifier l'enregistrement des captures d'écran Skip Menu and Generate Default Character - Passer le menu principal et générer un personnage standard + Passer le menu principal et générer un personnage standard Start Default Character at - Placer le personnage par défaut dans + Placer le personnage par défaut dans Run Script After Startup: - Script à lancer après démarrage : + Script à lancer après démarrage : + + + Controls the strength of the doppler effect. Zero means it is completely disabled. + + + + Doppler Factor + diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 879f24dc76..e06d26a5e3 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1054,47 +1054,47 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Interface - Интерфейс + Интерфейс <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - <html><head/><body><p>Эта настройка отвечает за масштабирование окон внутриигрового интерфейса. Значение 1.0 соответствует нормальному размеру интерфейса.</p></body></html> + <html><head/><body><p>Эта настройка отвечает за масштабирование окон внутриигрового интерфейса. Значение 1.0 соответствует нормальному размеру интерфейса.</p></body></html> GUI Scaling Factor - Масштаб интерфейса + Масштаб интерфейса <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - <html><head/><body><p>Когда настройка включена, в подсказке, всплывающей при наведении курсора на иконку магического эффекта, будет отображаться оставшаяся длительность магического эффекта или источника света.</p><p>По умолчанию настройка отключена.</p></body></html> + <html><head/><body><p>Когда настройка включена, в подсказке, всплывающей при наведении курсора на иконку магического эффекта, будет отображаться оставшаяся длительность магического эффекта или источника света.</p><p>По умолчанию настройка отключена.</p></body></html> Show Effect Duration - Показывать длительность эффектов + Показывать длительность эффектов <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Если эта настройка включена, у тем диалогов будет другой цвет, если у вашего собеседника есть уникальная реплика по заданной теме, или же если вы уже видели текст темы. Цвета могут быть настроены через settings.cfg.</p><p>По умолчанию настройка отключена.</p></body></html> + <html><head/><body><p>Если эта настройка включена, у тем диалогов будет другой цвет, если у вашего собеседника есть уникальная реплика по заданной теме, или же если вы уже видели текст темы. Цвета могут быть настроены через settings.cfg.</p><p>По умолчанию настройка отключена.</p></body></html> Change Dialogue Topic Color - Смена цвета тем для диалогов + Смена цвета тем для диалогов Size of characters in game texts. - Размер символов в текстах. + Размер символов в текстах. Font Size - Размер шрифтов + Размер шрифтов <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - <html><head/><body><p>Включить возможность масштабирования на локальной и глобальной картах.</p></body></html> + <html><head/><body><p>Включить возможность масштабирования на локальной и глобальной картах.</p></body></html> Can Zoom on Maps - Включить масштабирование карты + Включить масштабирование карты <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> @@ -1102,39 +1102,39 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках стрел и болтов будет показан их бонус к урону.</p><p>По умолчанию настройка выключена.</p></body></html> + <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках стрел и болтов будет показан их бонус к урону.</p><p>По умолчанию настройка выключена.</p></body></html> Show Projectile Damage - Показывать урон снарядов + Показывать урон снарядов <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках оружия ближнего боя будут показаны его дальность и скорость атаки.</p><p>По умолчанию настройка выключена.</p></body></html> + <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках оружия ближнего боя будут показаны его дальность и скорость атаки.</p><p>По умолчанию настройка выключена.</p></body></html> Show Melee Info - Показывать информацию об оружии + Показывать информацию об оружии <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - <html><head/><body><p>Растягивать фон меню, экранов загрузки и т.д., чтобы изображение соответствовало соотношению сторон выбранного разрешения экрана.</p></body></html> + <html><head/><body><p>Растягивать фон меню, экранов загрузки и т.д., чтобы изображение соответствовало соотношению сторон выбранного разрешения экрана.</p></body></html> Stretch Menu Background - Растягивать фон меню + Растягивать фон меню Show Owned Objects - Выделять объекты с владельцами + Выделять объекты с владельцами <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Показывать шанс успеха в меню зачарования, или же нет.</p><p>По умолчанию настройка выключена.</p></body></html> + <html><head/><body><p>Показывать шанс успеха в меню зачарования, или же нет.</p><p>По умолчанию настройка выключена.</p></body></html> Show Enchant Chance - Показывать шанс успеха зачарования + Показывать шанс успеха зачарования <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> @@ -1150,63 +1150,63 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Miscellaneous - Разное + Разное Saves - Сохранения + Сохранения JPG - JPG + JPG PNG - PNG + PNG TGA - TGA + TGA Notify on Saved Screenshot - Уведомление при сохранении снимка + Уведомление при сохранении снимка Testing - Отладка + Отладка These settings are intended for testing mods and will cause issues if used for normal gameplay. - Эти настройки предназначены для отладки модов, и при их использовании для нормального игрового процесса возникнут проблемы. + Эти настройки предназначены для отладки модов, и при их использовании для нормального игрового процесса возникнут проблемы. <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - <html><head/><body><p>Когда эта настройка включена, OpenMW будет управлять курсором мыши.</p><p>В “режиме обзора”, OpenMW будет захватывать курсор в центре экрана вне зависимости от значения этой настройки (потому что курсор всегда расположен по центру в окне OpenMW). Однако в режиме меню эта настройка определяет поведение выхода курсора за пределы окна OpenMW. Если настройка включена, курсор остановится на краю окна, предотвращая доступ к другим приложениям. Если выключена, курсор может свободно перемещаться по рабочему столу.</p><p>Эта настройка не применяется к экрану, на котором нажата клавиша “Escape” (там курсор никогда не захватывается). Вне зависимости от значения этой настройки “Alt-Tab” и некоторые другие зависимые от операционной системы комбинации клавиш могут быть использованы, чтобы вернуть управление курсором операционной системе. Эта настройка также взаимодействует с настройкой "minimize on focus loss", определяя, что именно считать потерей фокуса. На системах с двумя экранами может быть проще получить доступ ко второму экрану, если настройка выключена.</p><p>Замечание для разработчиков: лучше запускать игру с отключенной настройкой при запуске игры через отладчик, чтобы курсор не становился недоступен, когда игра останавливается на точке останова.</p></body></html> + <html><head/><body><p>Когда эта настройка включена, OpenMW будет управлять курсором мыши.</p><p>В “режиме обзора”, OpenMW будет захватывать курсор в центре экрана вне зависимости от значения этой настройки (потому что курсор всегда расположен по центру в окне OpenMW). Однако в режиме меню эта настройка определяет поведение выхода курсора за пределы окна OpenMW. Если настройка включена, курсор остановится на краю окна, предотвращая доступ к другим приложениям. Если выключена, курсор может свободно перемещаться по рабочему столу.</p><p>Эта настройка не применяется к экрану, на котором нажата клавиша “Escape” (там курсор никогда не захватывается). Вне зависимости от значения этой настройки “Alt-Tab” и некоторые другие зависимые от операционной системы комбинации клавиш могут быть использованы, чтобы вернуть управление курсором операционной системе. Эта настройка также взаимодействует с настройкой "minimize on focus loss", определяя, что именно считать потерей фокуса. На системах с двумя экранами может быть проще получить доступ ко второму экрану, если настройка выключена.</p><p>Замечание для разработчиков: лучше запускать игру с отключенной настройкой при запуске игры через отладчик, чтобы курсор не становился недоступен, когда игра останавливается на точке останова.</p></body></html> Grab Cursor - Захватывать курсор + Захватывать курсор Skip Menu and Generate Default Character - Пропустить меню и создать персонажа по умолчанию + Пропустить меню и создать персонажа по умолчанию Start Default Character at - Запустить с персонажем по умолчанию в локации + Запустить с персонажем по умолчанию в локации Default Cell - по умолчанию + по умолчанию Run Script After Startup: - Запустить скрипт после запуска: + Запустить скрипт после запуска: Browse… - Выбрать… + Выбрать… <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> @@ -1414,27 +1414,27 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Tooltip - Всплывающая подсказка + Всплывающая подсказка Crosshair - Прицел + Прицел Tooltip and Crosshair - Всплывающая подсказка и прицел + Всплывающая подсказка и прицел Maximum Quicksaves - Количество быстрых сохранений + Количество быстрых сохранений Screenshots - Снимки экрана + Снимки экрана Screenshot Format - Формат снимков экрана + Формат снимков экрана <html><head/><body><p>Set the internal handling of light sources.</p> @@ -1462,5 +1462,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Use the Camera as the Sound Listener Использовать камеру как слушателя + + Controls the strength of the doppler effect. Zero means it is completely disabled. + + + + Doppler Factor + + diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts index f2cca2346c..90aff98f08 100644 --- a/files/lang/launcher_sv.ts +++ b/files/lang/launcher_sv.ts @@ -883,27 +883,27 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Interface - Gränssnitt + Gränssnitt <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - <html><head/><body><p>Denna inställning skalar grafiska fönster i gränssnittet. Ett värde på 1.0 ger den normala skalan.</p></body></html> + <html><head/><body><p>Denna inställning skalar grafiska fönster i gränssnittet. Ett värde på 1.0 ger den normala skalan.</p></body></html> <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - <html><head/><body><p>Visar den återstående tiden för magiska effekter och ljus om denna inställning är på. Den återstående tiden visas som en inforuta när muspekaren befinner sig över den magiska effekten. </p><p>Förvalt är av.</p></body></html> + <html><head/><body><p>Visar den återstående tiden för magiska effekter och ljus om denna inställning är på. Den återstående tiden visas som en inforuta när muspekaren befinner sig över den magiska effekten. </p><p>Förvalt är av.</p></body></html> <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Om denna inställning är på kommer dialogämnen ha en annan färg om ämnet är specifikt till den icke-spelbara figur du pratar med eller om ämnet redan har setts. Färger kan ändras i settings.cfg.</p><p>Förvalt är av.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer dialogämnen ha en annan färg om ämnet är specifikt till den icke-spelbara figur du pratar med eller om ämnet redan har setts. Färger kan ändras i settings.cfg.</p><p>Förvalt är av.</p></body></html> Size of characters in game texts. - Storlek på tecken i speltext. + Storlek på tecken i speltext. <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - <html><head/><body><p>Aktivera zoomning på lokala och globala kartor.</p></body></html> + <html><head/><body><p>Aktivera zoomning på lokala och globala kartor.</p></body></html> <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> @@ -911,19 +911,19 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Om denna inställning är på kommer skadebonus från pilar visas på föremålens inforuta.</p><p>Förvalt är av.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer skadebonus från pilar visas på föremålens inforuta.</p><p>Förvalt är av.</p></body></html> <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Om denna inställning är på kommer närstridsvapens räckvidd och hastighet att visas på föremåls inforuta.</p><p>Förvalt är av.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer närstridsvapens räckvidd och hastighet att visas på föremåls inforuta.</p><p>Förvalt är av.</p></body></html> <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - <html><head/><body><p>Sträck ut menyer, laddskärmar o.s.v. till fönstrets aspektratio.</p></body></html> + <html><head/><body><p>Sträck ut menyer, laddskärmar o.s.v. till fönstrets aspektratio.</p></body></html> <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Huruvida chansen att lyckas kommer visas i förtrollningsmenyn.</p><p>Förvalt är av.</p></body></html> + <html><head/><body><p>Huruvida chansen att lyckas kommer visas i förtrollningsmenyn.</p><p>Förvalt är av.</p></body></html> <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> @@ -935,39 +935,39 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Miscellaneous - Diverse + Diverse Saves - Sparfiler + Sparfiler JPG - JPG + JPG PNG - PNG + PNG TGA - TGA + TGA Testing - Testning + Testning These settings are intended for testing mods and will cause issues if used for normal gameplay. - Dessa inställningar är avsedda för att testa moddar och kommer orsaka problem vid normalt spelande. + Dessa inställningar är avsedda för att testa moddar och kommer orsaka problem vid normalt spelande. <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - <html><head/><body><p>OpenMW kommer ta kontroll av muspekaren om denna inställning är aktiverad.</p><p>I ”tittläge” kommer OpenMW centrera muspekaren oavsett värdet på denna inställning (eftersom muspekaren/hårkorset alltid är centrerat i OpenMW-fönstret). I gränssnittsläge däremot kommer denna inställning bedöma beteendet när muspekaren flyttas utanför OpenMW-fönstret. Om på kommer muspekarrörelsen stanna vid kanten av fönstret, vilket förhindrar tillgång till andra applikationer. Om av tillåts muspekaren att röras fritt över skrivbordet.</p><p>Denna inställning appliceras inte på skärmen där Escape har blivit tryckt, då muspekaren aldrig tas över. Oavsett denna inställning kan ”Alt-Tab” eller annan operativsystemberoende knappsekvens användas för att ge operativsystemet åter tillgång till muspekaren. Denna inställning interagerar med minimera vid fokusförlust-inställningen genom att påverka vad som räknas som en fokusförlust. Specifikt på en tvåskärmskonfiguration kan det vara mer smidigt att få tillgång till den andra skärmen med inställningen inaktiverad.</p><p>Notis för utvecklare: det är önskvärt att ha denna inställning inaktiverad när OpenMW körs i debug-läge för att förhindra att musen blir oanvändbar när spelet pausar vid en brytpunkt.</p></body></html> + <html><head/><body><p>OpenMW kommer ta kontroll av muspekaren om denna inställning är aktiverad.</p><p>I ”tittläge” kommer OpenMW centrera muspekaren oavsett värdet på denna inställning (eftersom muspekaren/hårkorset alltid är centrerat i OpenMW-fönstret). I gränssnittsläge däremot kommer denna inställning bedöma beteendet när muspekaren flyttas utanför OpenMW-fönstret. Om på kommer muspekarrörelsen stanna vid kanten av fönstret, vilket förhindrar tillgång till andra applikationer. Om av tillåts muspekaren att röras fritt över skrivbordet.</p><p>Denna inställning appliceras inte på skärmen där Escape har blivit tryckt, då muspekaren aldrig tas över. Oavsett denna inställning kan ”Alt-Tab” eller annan operativsystemberoende knappsekvens användas för att ge operativsystemet åter tillgång till muspekaren. Denna inställning interagerar med minimera vid fokusförlust-inställningen genom att påverka vad som räknas som en fokusförlust. Specifikt på en tvåskärmskonfiguration kan det vara mer smidigt att få tillgång till den andra skärmen med inställningen inaktiverad.</p><p>Notis för utvecklare: det är önskvärt att ha denna inställning inaktiverad när OpenMW körs i debug-läge för att förhindra att musen blir oanvändbar när spelet pausar vid en brytpunkt.</p></body></html> Browse… - Bläddra… + Bläddra… Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. @@ -1059,15 +1059,15 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Tooltip - Inforuta + Inforuta Crosshair - Hårkors + Hårkors Screenshots - Skärmdumpar + Skärmdumpar <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> @@ -1332,59 +1332,59 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Tooltip and Crosshair - Inforuta och hårkors + Inforuta och hårkors GUI Scaling Factor - Skalningsfaktor för gränssnitt + Skalningsfaktor för gränssnitt Show Effect Duration - Visa effektvaraktighet + Visa effektvaraktighet Change Dialogue Topic Color - Ändra färg på dialogämnen + Ändra färg på dialogämnen Font Size - Fontstorlek + Fontstorlek Show Projectile Damage - Visa projektilskada + Visa projektilskada Show Melee Info - Visa närstridsinfo + Visa närstridsinfo Stretch Menu Background - Sträck ut menybakgrund + Sträck ut menybakgrund Show Owned Objects - Visa ägda objekt + Visa ägda objekt Show Enchant Chance - Visa chans för "Enchant" + Visa chans för "Enchant" Maximum Quicksaves - Max snabbsparfiler + Max snabbsparfiler Screenshot Format - Skärmdumpformat + Skärmdumpformat Grab Cursor - Ta över muspekaren + Ta över muspekaren Default Cell - Förinställd cell + Förinställd cell Bounds @@ -1440,23 +1440,23 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Can Zoom on Maps - Kan zooma på kartor + Kan zooma på kartor Notify on Saved Screenshot - Ge notis vid sparad skärmdump + Ge notis vid sparad skärmdump Skip Menu and Generate Default Character - Hoppa över meny och generera förinställd rollfigur + Hoppa över meny och generera förinställd rollfigur Start Default Character at - Starta förinställd rollfigur vid + Starta förinställd rollfigur vid Run Script After Startup: - Kör skript efter uppstart: + Kör skript efter uppstart: Smooth Animation Transitions @@ -1466,5 +1466,13 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> <html><head/><body><p>Vid aktivering gör denna funktion att övergångarna mellan olika animationer och poser blir mycket mjukare. Funktionen gör det också möjligt att konfigurera animationsövergångarna i YAML-filer. Dessa filer kan buntas ihop tillsammans med nya animationsfiler.</p></body></html> + + Controls the strength of the doppler effect. Zero means it is completely disabled. + + + + Doppler Factor + + From f0e9df4de2098ac015af266d498d61a563f6b455 Mon Sep 17 00:00:00 2001 From: epochwon Date: Sat, 3 May 2025 21:19:06 -0400 Subject: [PATCH 22/55] fix launcher ui and lang mess --- apps/launcher/ui/settingspage.ui | 1 + files/lang/launcher_de.ts | 164 +++++++++++++++++++++++++++++++ files/lang/launcher_en.ts | 164 +++++++++++++++++++++++++++++++ files/lang/launcher_fr.ts | 82 ++++++++-------- files/lang/launcher_ru.ts | 82 ++++++++-------- files/lang/launcher_sv.ts | 82 ++++++++-------- 6 files changed, 452 insertions(+), 123 deletions(-) diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index c383a6abbf..6abbc3428d 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -1279,6 +1279,7 @@ + Qt::Vertical diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index bb53c32ce2..c7b22be633 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -876,10 +876,50 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Select your preferred HRTF profile. + + Interface + + + + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + + + + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + + + + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + + + + Size of characters in game texts. + + + + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + + + + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> @@ -888,6 +928,42 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + + Miscellaneous + + + + Saves + + + + JPG + + + + PNG + + + + TGA + + + + Testing + + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + + + + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + + + + Browse… + + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. @@ -976,6 +1052,18 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Lighting + + Tooltip + + + + Crosshair + + + + Screenshots + + <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> @@ -1231,6 +1319,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov HRTF Profile + + Tooltip and Crosshair + + + + GUI Scaling Factor + + + + Show Effect Duration + + + + Change Dialogue Topic Color + + + + Font Size + + + + Show Projectile Damage + + + + Show Melee Info + + + + Stretch Menu Background + + + + Show Owned Objects + + + + Show Enchant Chance + + + + Maximum Quicksaves + + + + Screenshot Format + + + + Grab Cursor + + + + Default Cell + + Bounds @@ -1283,6 +1427,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Use the Camera as the Sound Listener + + Can Zoom on Maps + + + + Notify on Saved Screenshot + + + + Skip Menu and Generate Default Character + + + + Start Default Character at + + + + Run Script After Startup: + + Controls the strength of the doppler effect. Zero means it is completely disabled. diff --git a/files/lang/launcher_en.ts b/files/lang/launcher_en.ts index 5d9fd74b33..7c29749b15 100644 --- a/files/lang/launcher_en.ts +++ b/files/lang/launcher_en.ts @@ -1275,6 +1275,170 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Use the Camera as the Sound Listener + + Interface + + + + Tooltip + + + + Crosshair + + + + Tooltip and Crosshair + + + + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + + + + GUI Scaling Factor + + + + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + + + + Show Effect Duration + + + + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + + + + Change Dialogue Topic Color + + + + Size of characters in game texts. + + + + Font Size + + + + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + + + + Can Zoom on Maps + + + + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + + Show Projectile Damage + + + + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + + Show Melee Info + + + + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + + + + Stretch Menu Background + + + + Show Owned Objects + + + + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + + + + Show Enchant Chance + + + + Miscellaneous + + + + Saves + + + + Maximum Quicksaves + + + + Screenshots + + + + Screenshot Format + + + + JPG + + + + PNG + + + + TGA + + + + Notify on Saved Screenshot + + + + Testing + + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + + + + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + + + + Grab Cursor + + + + Skip Menu and Generate Default Character + + + + Start Default Character at + + + + Default Cell + + + + Run Script After Startup: + + + + Browse… + + Smooth Animation Transitions diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 546e9d3f18..f81c3d78d5 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -878,27 +878,27 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Interface - Interface + Interface <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - <html><body><p>Cette option agrandit ou diminue l'interface utilisateur. Une valeur de 1 correspond à sa taille nominale.</p></body></html> + <html><body><p>Cette option agrandit ou diminue l'interface utilisateur. Une valeur de 1 correspond à sa taille nominale.</p></body></html> <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - <html><body><p>Cette option permet d'activer la durée de vie restante des effets magiques et des lampes. Cette durée restante s'affiche en passant la souris au-dessus de l'icône de l'effet magique.</p></body></html> + <html><body><p>Cette option permet d'activer la durée de vie restante des effets magiques et des lampes. Cette durée restante s'affiche en passant la souris au-dessus de l'icône de l'effet magique.</p></body></html> <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - <html><body><p>Lorsque cette option est activée, les sujets de conversation de la fenêtre de dialogue auront des couleurs différentes si ceux-ci sont spécifiques au PNJ ou qu'ils ont déjà été lus autre part. Ces couleurs peuvent être changées dans le fichier settings.cfg.</p></body></html> + <html><body><p>Lorsque cette option est activée, les sujets de conversation de la fenêtre de dialogue auront des couleurs différentes si ceux-ci sont spécifiques au PNJ ou qu'ils ont déjà été lus autre part. Ces couleurs peuvent être changées dans le fichier settings.cfg.</p></body></html> Size of characters in game texts. - <html><body><p>Taille des caractères des textes du jeu.</p></body></html> + <html><body><p>Taille des caractères des textes du jeu.</p></body></html> <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - <html><body><p>Cette option permet de zoomer en jeu sur la carte (carte locale et carte du monde).</p></body></html> + <html><body><p>Cette option permet de zoomer en jeu sur la carte (carte locale et carte du monde).</p></body></html> <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> @@ -906,19 +906,19 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><body><p>Lorsque cette option est activée, les bonus de dommages des flèches et des carreaux apparaissent dans l'infobulle de l'objet.</p></body></html> + <html><body><p>Lorsque cette option est activée, les bonus de dommages des flèches et des carreaux apparaissent dans l'infobulle de l'objet.</p></body></html> <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><body><p>Lorsque cette option est activée, la portée et la rapidité d'attaque des armes de mêlées apparaissent dans l'infobulle de l'objet.</p></body></html> + <html><body><p>Lorsque cette option est activée, la portée et la rapidité d'attaque des armes de mêlées apparaissent dans l'infobulle de l'objet.</p></body></html> <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - <html><body><p>Étends les menus, les écrans de chargement et autres éléments d'interface aux formats d'image du jeu.</p></body></html> + <html><body><p>Étends les menus, les écrans de chargement et autres éléments d'interface aux formats d'image du jeu.</p></body></html> <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - <html><body><p>Lorsque l'option est activée, affiche les chances de succès d'un enchantement dans le menu des enchantements.</body></html> + <html><body><p>Lorsque l'option est activée, affiche les chances de succès d'un enchantement dans le menu des enchantements.</body></html> <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> @@ -930,39 +930,39 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Miscellaneous - Divers + Divers Saves - Sauvegardes + Sauvegardes JPG - JPG + JPG PNG - PNG + PNG TGA - TGA + TGA Testing - Testeurs + Testeurs These settings are intended for testing mods and will cause issues if used for normal gameplay. - Ces options sont destinées aux testeurs de contenus additionnels (mods). Ils poseront problème lors d'une partie normale. + Ces options sont destinées aux testeurs de contenus additionnels (mods). Ils poseront problème lors d'une partie normale. <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - <html><body><p>Lorsque cette option est activée, OpenMW capture le curseur de la souris.</p><p>Lorsque le jeu est en mode "Regard/Déplacement", OpenMW centre le curseur au milieu de l'écran; sans tenir compte de cette option (le curseur/viseur est toujours au centre de la fenêtre d'OpenMW). Néanmoins, lorsqu'un menu est ouvert, cette option permet de récupérer le contrôle de la souris. En effet, lorsque l'option est active, le mouvement du curseur s'arrête aux bords de la fenêtre et empêche d'accéder à d'autres applications. Tandis que, lorsque l'option est désactivée, le curseur peut sortir librement de la fenêtre et accéder au bureau.</p><p>Cette option ne s'applique pas à l'écran lorsque la touche Échap a été enfoncée, dans ce cas, le curseur n'est jamais capturé. Cette option n'agit pas non plus sur "Atl-Tab" ou toute autre séquence de touches utilisée par le système d'exploitation pour récupérer le contrôle du curseur de la souris. </p><p>Cette option interagit avec l'option de minimisation de la fenêtre lors de la perte de focus en modifiant ce qui est considéré comme une perte de focus. Par exemple, avec une configuration à doubles écrans, il peut être plus agréable d'accéder au second écran avec cette option désactivée.</p><p>Note aux développeurs : Il est préférable de désactiver cette option lorsque le jeu tourne dans un débogueur, ceci afin d'empêcher le curseur de la souris de devenir inutilisable lorsque le jeu s'arrête sur un breakpoint.</p></body></html> + <html><body><p>Lorsque cette option est activée, OpenMW capture le curseur de la souris.</p><p>Lorsque le jeu est en mode "Regard/Déplacement", OpenMW centre le curseur au milieu de l'écran; sans tenir compte de cette option (le curseur/viseur est toujours au centre de la fenêtre d'OpenMW). Néanmoins, lorsqu'un menu est ouvert, cette option permet de récupérer le contrôle de la souris. En effet, lorsque l'option est active, le mouvement du curseur s'arrête aux bords de la fenêtre et empêche d'accéder à d'autres applications. Tandis que, lorsque l'option est désactivée, le curseur peut sortir librement de la fenêtre et accéder au bureau.</p><p>Cette option ne s'applique pas à l'écran lorsque la touche Échap a été enfoncée, dans ce cas, le curseur n'est jamais capturé. Cette option n'agit pas non plus sur "Atl-Tab" ou toute autre séquence de touches utilisée par le système d'exploitation pour récupérer le contrôle du curseur de la souris. </p><p>Cette option interagit avec l'option de minimisation de la fenêtre lors de la perte de focus en modifiant ce qui est considéré comme une perte de focus. Par exemple, avec une configuration à doubles écrans, il peut être plus agréable d'accéder au second écran avec cette option désactivée.</p><p>Note aux développeurs : Il est préférable de désactiver cette option lorsque le jeu tourne dans un débogueur, ceci afin d'empêcher le curseur de la souris de devenir inutilisable lorsque le jeu s'arrête sur un breakpoint.</p></body></html> Browse… - Parcourir... + Parcourir... Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. @@ -1054,15 +1054,15 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Tooltip - Infobulles + Infobulles Crosshair - Réticule de visée + Réticule de visée Screenshots - Captures d'écran + Captures d'écran <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> @@ -1324,59 +1324,59 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Tooltip and Crosshair - Infobulles et réticule de visée + Infobulles et réticule de visée GUI Scaling Factor - Échelle de l'interface + Échelle de l'interface Show Effect Duration - Afficher la durée des effets + Afficher la durée des effets Change Dialogue Topic Color - Changer la couleur des sujets de conversation + Changer la couleur des sujets de conversation Font Size - Taille des polices + Taille des polices Show Projectile Damage - Afficher les dommages des projectiles + Afficher les dommages des projectiles Show Melee Info - Afficher les infos de mêlée + Afficher les infos de mêlée Stretch Menu Background - Étendre les arrière-plans + Étendre les arrière-plans Show Owned Objects - Afficher la possession des objets + Afficher la possession des objets Show Enchant Chance - Afficher les chances d'enchantement + Afficher les chances d'enchantement Maximum Quicksaves - Nombre maximum de sauvegardes rapides + Nombre maximum de sauvegardes rapides Screenshot Format - Format des captures + Format des captures Grab Cursor - Capturer le curseur + Capturer le curseur Default Cell - la cellule par défaut + la cellule par défaut Bounds @@ -1432,23 +1432,23 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Can Zoom on Maps - Permettre le zoom sur la carte + Permettre le zoom sur la carte Notify on Saved Screenshot - Notifier l'enregistrement des captures d'écran + Notifier l'enregistrement des captures d'écran Skip Menu and Generate Default Character - Passer le menu principal et générer un personnage standard + Passer le menu principal et générer un personnage standard Start Default Character at - Placer le personnage par défaut dans + Placer le personnage par défaut dans Run Script After Startup: - Script à lancer après démarrage : + Script à lancer après démarrage : Controls the strength of the doppler effect. Zero means it is completely disabled. diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index e06d26a5e3..71bbbf4f4a 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1054,47 +1054,47 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Interface - Интерфейс + Интерфейс <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - <html><head/><body><p>Эта настройка отвечает за масштабирование окон внутриигрового интерфейса. Значение 1.0 соответствует нормальному размеру интерфейса.</p></body></html> + <html><head/><body><p>Эта настройка отвечает за масштабирование окон внутриигрового интерфейса. Значение 1.0 соответствует нормальному размеру интерфейса.</p></body></html> GUI Scaling Factor - Масштаб интерфейса + Масштаб интерфейса <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - <html><head/><body><p>Когда настройка включена, в подсказке, всплывающей при наведении курсора на иконку магического эффекта, будет отображаться оставшаяся длительность магического эффекта или источника света.</p><p>По умолчанию настройка отключена.</p></body></html> + <html><head/><body><p>Когда настройка включена, в подсказке, всплывающей при наведении курсора на иконку магического эффекта, будет отображаться оставшаяся длительность магического эффекта или источника света.</p><p>По умолчанию настройка отключена.</p></body></html> Show Effect Duration - Показывать длительность эффектов + Показывать длительность эффектов <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Если эта настройка включена, у тем диалогов будет другой цвет, если у вашего собеседника есть уникальная реплика по заданной теме, или же если вы уже видели текст темы. Цвета могут быть настроены через settings.cfg.</p><p>По умолчанию настройка отключена.</p></body></html> + <html><head/><body><p>Если эта настройка включена, у тем диалогов будет другой цвет, если у вашего собеседника есть уникальная реплика по заданной теме, или же если вы уже видели текст темы. Цвета могут быть настроены через settings.cfg.</p><p>По умолчанию настройка отключена.</p></body></html> Change Dialogue Topic Color - Смена цвета тем для диалогов + Смена цвета тем для диалогов Size of characters in game texts. - Размер символов в текстах. + Размер символов в текстах. Font Size - Размер шрифтов + Размер шрифтов <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - <html><head/><body><p>Включить возможность масштабирования на локальной и глобальной картах.</p></body></html> + <html><head/><body><p>Включить возможность масштабирования на локальной и глобальной картах.</p></body></html> Can Zoom on Maps - Включить масштабирование карты + Включить масштабирование карты <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> @@ -1102,39 +1102,39 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках стрел и болтов будет показан их бонус к урону.</p><p>По умолчанию настройка выключена.</p></body></html> + <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках стрел и болтов будет показан их бонус к урону.</p><p>По умолчанию настройка выключена.</p></body></html> Show Projectile Damage - Показывать урон снарядов + Показывать урон снарядов <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках оружия ближнего боя будут показаны его дальность и скорость атаки.</p><p>По умолчанию настройка выключена.</p></body></html> + <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках оружия ближнего боя будут показаны его дальность и скорость атаки.</p><p>По умолчанию настройка выключена.</p></body></html> Show Melee Info - Показывать информацию об оружии + Показывать информацию об оружии <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - <html><head/><body><p>Растягивать фон меню, экранов загрузки и т.д., чтобы изображение соответствовало соотношению сторон выбранного разрешения экрана.</p></body></html> + <html><head/><body><p>Растягивать фон меню, экранов загрузки и т.д., чтобы изображение соответствовало соотношению сторон выбранного разрешения экрана.</p></body></html> Stretch Menu Background - Растягивать фон меню + Растягивать фон меню Show Owned Objects - Выделять объекты с владельцами + Выделять объекты с владельцами <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Показывать шанс успеха в меню зачарования, или же нет.</p><p>По умолчанию настройка выключена.</p></body></html> + <html><head/><body><p>Показывать шанс успеха в меню зачарования, или же нет.</p><p>По умолчанию настройка выключена.</p></body></html> Show Enchant Chance - Показывать шанс успеха зачарования + Показывать шанс успеха зачарования <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> @@ -1150,63 +1150,63 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Miscellaneous - Разное + Разное Saves - Сохранения + Сохранения JPG - JPG + JPG PNG - PNG + PNG TGA - TGA + TGA Notify on Saved Screenshot - Уведомление при сохранении снимка + Уведомление при сохранении снимка Testing - Отладка + Отладка These settings are intended for testing mods and will cause issues if used for normal gameplay. - Эти настройки предназначены для отладки модов, и при их использовании для нормального игрового процесса возникнут проблемы. + Эти настройки предназначены для отладки модов, и при их использовании для нормального игрового процесса возникнут проблемы. <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - <html><head/><body><p>Когда эта настройка включена, OpenMW будет управлять курсором мыши.</p><p>В “режиме обзора”, OpenMW будет захватывать курсор в центре экрана вне зависимости от значения этой настройки (потому что курсор всегда расположен по центру в окне OpenMW). Однако в режиме меню эта настройка определяет поведение выхода курсора за пределы окна OpenMW. Если настройка включена, курсор остановится на краю окна, предотвращая доступ к другим приложениям. Если выключена, курсор может свободно перемещаться по рабочему столу.</p><p>Эта настройка не применяется к экрану, на котором нажата клавиша “Escape” (там курсор никогда не захватывается). Вне зависимости от значения этой настройки “Alt-Tab” и некоторые другие зависимые от операционной системы комбинации клавиш могут быть использованы, чтобы вернуть управление курсором операционной системе. Эта настройка также взаимодействует с настройкой "minimize on focus loss", определяя, что именно считать потерей фокуса. На системах с двумя экранами может быть проще получить доступ ко второму экрану, если настройка выключена.</p><p>Замечание для разработчиков: лучше запускать игру с отключенной настройкой при запуске игры через отладчик, чтобы курсор не становился недоступен, когда игра останавливается на точке останова.</p></body></html> + <html><head/><body><p>Когда эта настройка включена, OpenMW будет управлять курсором мыши.</p><p>В “режиме обзора”, OpenMW будет захватывать курсор в центре экрана вне зависимости от значения этой настройки (потому что курсор всегда расположен по центру в окне OpenMW). Однако в режиме меню эта настройка определяет поведение выхода курсора за пределы окна OpenMW. Если настройка включена, курсор остановится на краю окна, предотвращая доступ к другим приложениям. Если выключена, курсор может свободно перемещаться по рабочему столу.</p><p>Эта настройка не применяется к экрану, на котором нажата клавиша “Escape” (там курсор никогда не захватывается). Вне зависимости от значения этой настройки “Alt-Tab” и некоторые другие зависимые от операционной системы комбинации клавиш могут быть использованы, чтобы вернуть управление курсором операционной системе. Эта настройка также взаимодействует с настройкой "minimize on focus loss", определяя, что именно считать потерей фокуса. На системах с двумя экранами может быть проще получить доступ ко второму экрану, если настройка выключена.</p><p>Замечание для разработчиков: лучше запускать игру с отключенной настройкой при запуске игры через отладчик, чтобы курсор не становился недоступен, когда игра останавливается на точке останова.</p></body></html> Grab Cursor - Захватывать курсор + Захватывать курсор Skip Menu and Generate Default Character - Пропустить меню и создать персонажа по умолчанию + Пропустить меню и создать персонажа по умолчанию Start Default Character at - Запустить с персонажем по умолчанию в локации + Запустить с персонажем по умолчанию в локации Default Cell - по умолчанию + по умолчанию Run Script After Startup: - Запустить скрипт после запуска: + Запустить скрипт после запуска: Browse… - Выбрать… + Выбрать… <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> @@ -1414,27 +1414,27 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Tooltip - Всплывающая подсказка + Всплывающая подсказка Crosshair - Прицел + Прицел Tooltip and Crosshair - Всплывающая подсказка и прицел + Всплывающая подсказка и прицел Maximum Quicksaves - Количество быстрых сохранений + Количество быстрых сохранений Screenshots - Снимки экрана + Снимки экрана Screenshot Format - Формат снимков экрана + Формат снимков экрана <html><head/><body><p>Set the internal handling of light sources.</p> diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts index 90aff98f08..ca822f55b8 100644 --- a/files/lang/launcher_sv.ts +++ b/files/lang/launcher_sv.ts @@ -883,27 +883,27 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Interface - Gränssnitt + Gränssnitt <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - <html><head/><body><p>Denna inställning skalar grafiska fönster i gränssnittet. Ett värde på 1.0 ger den normala skalan.</p></body></html> + <html><head/><body><p>Denna inställning skalar grafiska fönster i gränssnittet. Ett värde på 1.0 ger den normala skalan.</p></body></html> <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - <html><head/><body><p>Visar den återstående tiden för magiska effekter och ljus om denna inställning är på. Den återstående tiden visas som en inforuta när muspekaren befinner sig över den magiska effekten. </p><p>Förvalt är av.</p></body></html> + <html><head/><body><p>Visar den återstående tiden för magiska effekter och ljus om denna inställning är på. Den återstående tiden visas som en inforuta när muspekaren befinner sig över den magiska effekten. </p><p>Förvalt är av.</p></body></html> <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Om denna inställning är på kommer dialogämnen ha en annan färg om ämnet är specifikt till den icke-spelbara figur du pratar med eller om ämnet redan har setts. Färger kan ändras i settings.cfg.</p><p>Förvalt är av.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer dialogämnen ha en annan färg om ämnet är specifikt till den icke-spelbara figur du pratar med eller om ämnet redan har setts. Färger kan ändras i settings.cfg.</p><p>Förvalt är av.</p></body></html> Size of characters in game texts. - Storlek på tecken i speltext. + Storlek på tecken i speltext. <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - <html><head/><body><p>Aktivera zoomning på lokala och globala kartor.</p></body></html> + <html><head/><body><p>Aktivera zoomning på lokala och globala kartor.</p></body></html> <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> @@ -911,19 +911,19 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Om denna inställning är på kommer skadebonus från pilar visas på föremålens inforuta.</p><p>Förvalt är av.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer skadebonus från pilar visas på föremålens inforuta.</p><p>Förvalt är av.</p></body></html> <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Om denna inställning är på kommer närstridsvapens räckvidd och hastighet att visas på föremåls inforuta.</p><p>Förvalt är av.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer närstridsvapens räckvidd och hastighet att visas på föremåls inforuta.</p><p>Förvalt är av.</p></body></html> <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - <html><head/><body><p>Sträck ut menyer, laddskärmar o.s.v. till fönstrets aspektratio.</p></body></html> + <html><head/><body><p>Sträck ut menyer, laddskärmar o.s.v. till fönstrets aspektratio.</p></body></html> <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Huruvida chansen att lyckas kommer visas i förtrollningsmenyn.</p><p>Förvalt är av.</p></body></html> + <html><head/><body><p>Huruvida chansen att lyckas kommer visas i förtrollningsmenyn.</p><p>Förvalt är av.</p></body></html> <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> @@ -935,39 +935,39 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Miscellaneous - Diverse + Diverse Saves - Sparfiler + Sparfiler JPG - JPG + JPG PNG - PNG + PNG TGA - TGA + TGA Testing - Testning + Testning These settings are intended for testing mods and will cause issues if used for normal gameplay. - Dessa inställningar är avsedda för att testa moddar och kommer orsaka problem vid normalt spelande. + Dessa inställningar är avsedda för att testa moddar och kommer orsaka problem vid normalt spelande. <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - <html><head/><body><p>OpenMW kommer ta kontroll av muspekaren om denna inställning är aktiverad.</p><p>I ”tittläge” kommer OpenMW centrera muspekaren oavsett värdet på denna inställning (eftersom muspekaren/hårkorset alltid är centrerat i OpenMW-fönstret). I gränssnittsläge däremot kommer denna inställning bedöma beteendet när muspekaren flyttas utanför OpenMW-fönstret. Om på kommer muspekarrörelsen stanna vid kanten av fönstret, vilket förhindrar tillgång till andra applikationer. Om av tillåts muspekaren att röras fritt över skrivbordet.</p><p>Denna inställning appliceras inte på skärmen där Escape har blivit tryckt, då muspekaren aldrig tas över. Oavsett denna inställning kan ”Alt-Tab” eller annan operativsystemberoende knappsekvens användas för att ge operativsystemet åter tillgång till muspekaren. Denna inställning interagerar med minimera vid fokusförlust-inställningen genom att påverka vad som räknas som en fokusförlust. Specifikt på en tvåskärmskonfiguration kan det vara mer smidigt att få tillgång till den andra skärmen med inställningen inaktiverad.</p><p>Notis för utvecklare: det är önskvärt att ha denna inställning inaktiverad när OpenMW körs i debug-läge för att förhindra att musen blir oanvändbar när spelet pausar vid en brytpunkt.</p></body></html> + <html><head/><body><p>OpenMW kommer ta kontroll av muspekaren om denna inställning är aktiverad.</p><p>I ”tittläge” kommer OpenMW centrera muspekaren oavsett värdet på denna inställning (eftersom muspekaren/hårkorset alltid är centrerat i OpenMW-fönstret). I gränssnittsläge däremot kommer denna inställning bedöma beteendet när muspekaren flyttas utanför OpenMW-fönstret. Om på kommer muspekarrörelsen stanna vid kanten av fönstret, vilket förhindrar tillgång till andra applikationer. Om av tillåts muspekaren att röras fritt över skrivbordet.</p><p>Denna inställning appliceras inte på skärmen där Escape har blivit tryckt, då muspekaren aldrig tas över. Oavsett denna inställning kan ”Alt-Tab” eller annan operativsystemberoende knappsekvens användas för att ge operativsystemet åter tillgång till muspekaren. Denna inställning interagerar med minimera vid fokusförlust-inställningen genom att påverka vad som räknas som en fokusförlust. Specifikt på en tvåskärmskonfiguration kan det vara mer smidigt att få tillgång till den andra skärmen med inställningen inaktiverad.</p><p>Notis för utvecklare: det är önskvärt att ha denna inställning inaktiverad när OpenMW körs i debug-läge för att förhindra att musen blir oanvändbar när spelet pausar vid en brytpunkt.</p></body></html> Browse… - Bläddra… + Bläddra… Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. @@ -1059,15 +1059,15 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Tooltip - Inforuta + Inforuta Crosshair - Hårkors + Hårkors Screenshots - Skärmdumpar + Skärmdumpar <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> @@ -1332,59 +1332,59 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Tooltip and Crosshair - Inforuta och hårkors + Inforuta och hårkors GUI Scaling Factor - Skalningsfaktor för gränssnitt + Skalningsfaktor för gränssnitt Show Effect Duration - Visa effektvaraktighet + Visa effektvaraktighet Change Dialogue Topic Color - Ändra färg på dialogämnen + Ändra färg på dialogämnen Font Size - Fontstorlek + Fontstorlek Show Projectile Damage - Visa projektilskada + Visa projektilskada Show Melee Info - Visa närstridsinfo + Visa närstridsinfo Stretch Menu Background - Sträck ut menybakgrund + Sträck ut menybakgrund Show Owned Objects - Visa ägda objekt + Visa ägda objekt Show Enchant Chance - Visa chans för "Enchant" + Visa chans för "Enchant" Maximum Quicksaves - Max snabbsparfiler + Max snabbsparfiler Screenshot Format - Skärmdumpformat + Skärmdumpformat Grab Cursor - Ta över muspekaren + Ta över muspekaren Default Cell - Förinställd cell + Förinställd cell Bounds @@ -1440,23 +1440,23 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Can Zoom on Maps - Kan zooma på kartor + Kan zooma på kartor Notify on Saved Screenshot - Ge notis vid sparad skärmdump + Ge notis vid sparad skärmdump Skip Menu and Generate Default Character - Hoppa över meny och generera förinställd rollfigur + Hoppa över meny och generera förinställd rollfigur Start Default Character at - Starta förinställd rollfigur vid + Starta förinställd rollfigur vid Run Script After Startup: - Kör skript efter uppstart: + Kör skript efter uppstart: Smooth Animation Transitions From ce666db34f6e30583f77ef9db08888e83735c8d8 Mon Sep 17 00:00:00 2001 From: epochwon Date: Sun, 4 May 2025 14:24:34 -0400 Subject: [PATCH 23/55] don't use EFX doppler, set doppler factor globally instead of per-sound --- apps/openmw/mwsound/openaloutput.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/openaloutput.cpp b/apps/openmw/mwsound/openaloutput.cpp index a586fc611a..e861545460 100644 --- a/apps/openmw/mwsound/openaloutput.cpp +++ b/apps/openmw/mwsound/openaloutput.cpp @@ -964,6 +964,7 @@ namespace MWSound // Speed of sound is in units per second. Take the sound speed in air (assumed // meters per second), multiply by the units per meter to get the speed in u/s. alSpeedOfSound(Constants::SoundSpeedInAir * Constants::UnitsPerMeter); + alDopplerFactor(Settings::sound().mDopplerFactor); alGetError(); mInitialized = true; @@ -1138,7 +1139,6 @@ namespace MWSound alSourcef(source, AL_GAIN, gain); alSourcef(source, AL_PITCH, pitch); - alSourcef(source, AL_DOPPLER_FACTOR, 0.0f); alSourcefv(source, AL_POSITION, pos.ptr()); alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -1179,7 +1179,6 @@ namespace MWSound alSourcef(source, AL_GAIN, gain); alSourcef(source, AL_PITCH, pitch); - alSourcef(source, AL_DOPPLER_FACTOR, Settings::sound().mDopplerFactor); alSourcefv(source, AL_POSITION, pos.ptr()); alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSourcefv(source, AL_VELOCITY, vel.ptr()); From ba361302a17a7804d330eebec5186dab46d04556 Mon Sep 17 00:00:00 2001 From: epochwon Date: Thu, 8 May 2025 18:08:46 -0400 Subject: [PATCH 24/55] cache physics framerate at start instead of checking every update --- apps/openmw/mwsound/soundmanagerimp.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index f7dff94d1c..3933af1523 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -43,6 +43,8 @@ namespace MWSound constexpr float sSfxFadeOutDuration = 1.0f; constexpr float sSoundCullDistance = 2000.f; + float physicsFramerate = 60.f; + WaterSoundUpdaterSettings makeWaterSoundUpdaterSettings() { WaterSoundUpdaterSettings settings; @@ -140,6 +142,13 @@ namespace MWSound return; } + if (const char* env = getenv("OPENMW_PHYSICS_FPS")) + { + if (const auto physFramerate = Misc::StringUtils::toNumeric(env); + physFramerate.has_value() && *physFramerate > 0) + physicsFramerate = *physFramerate; + } + std::vector names = mOutput->enumerate(); std::stringstream stream; @@ -961,14 +970,6 @@ namespace MWSound mUnderwaterSound = nullptr; } - float physicsFramerate = 60.f; - if (const char* env = getenv("OPENMW_PHYSICS_FPS")) - { - if (const auto physFramerate = Misc::StringUtils::toNumeric(env); - physFramerate.has_value() && *physFramerate > 0) - physicsFramerate = *physFramerate; - } - mOutput->startUpdate(); mOutput->updateListener(mListenerPos, mListenerDir, mListenerUp, mListenerVel, env); From db22706e8e8a725b9203d671f06da2fab8cf59f3 Mon Sep 17 00:00:00 2001 From: epochwon Date: Wed, 2 Jul 2025 17:28:53 -0400 Subject: [PATCH 25/55] I'm sorry clang, forgive me --- apps/openmw/mwsound/openaloutput.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/openaloutput.cpp b/apps/openmw/mwsound/openaloutput.cpp index e861545460..58f78ac280 100644 --- a/apps/openmw/mwsound/openaloutput.cpp +++ b/apps/openmw/mwsound/openaloutput.cpp @@ -1144,8 +1144,8 @@ namespace MWSound alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); } - void OpenALOutput::initCommon3D(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat mindist, ALfloat maxdist, - ALfloat gain, ALfloat pitch, bool loop, bool useenv) + void OpenALOutput::initCommon3D(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat mindist, + ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv) { alSourcef(source, AL_REFERENCE_DISTANCE, mindist); alSourcef(source, AL_MAX_DISTANCE, maxdist); @@ -1250,8 +1250,9 @@ namespace MWSound } source = mFreeSources.front(); - initCommon3D(source, sound->getPosition(), sound->getVelocity(), sound->getMinDistance(), sound->getMaxDistance(), - sound->getRealVolume(), getTimeScaledPitch(sound), sound->getIsLooping(), sound->getUseEnv()); + initCommon3D(source, sound->getPosition(), sound->getVelocity(), sound->getMinDistance(), + sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), sound->getIsLooping(), + sound->getUseEnv()); alSourcei(source, AL_BUFFER, GET_PTRID(data)); alSourcef(source, AL_SEC_OFFSET, offset); if (getALError() != AL_NO_ERROR) From 57280eaf9d1014ad0f52ff702d8b0f745836f5bd Mon Sep 17 00:00:00 2001 From: epochwon Date: Wed, 30 Jul 2025 10:28:41 -0400 Subject: [PATCH 26/55] Update Doppler description + Russian line --- apps/launcher/ui/settingspage.ui | 2 +- docs/source/reference/modding/settings/sound.rst | 4 ++-- files/lang/launcher_de.ts | 2 +- files/lang/launcher_en.ts | 2 +- files/lang/launcher_fr.ts | 2 +- files/lang/launcher_ru.ts | 4 ++-- files/lang/launcher_sv.ts | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 6abbc3428d..c9ec5b1109 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -1239,7 +1239,7 @@ - Controls the strength of the doppler effect. Zero means it is completely disabled. + <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> Doppler Factor diff --git a/docs/source/reference/modding/settings/sound.rst b/docs/source/reference/modding/settings/sound.rst index b462428ad1..4e73df989c 100644 --- a/docs/source/reference/modding/settings/sound.rst +++ b/docs/source/reference/modding/settings/sound.rst @@ -129,5 +129,5 @@ Sound Settings :default: 0.25 :location: :bdg-success:`Launcher > Settings > Audio` - This setting controls the strength of the doppler effect. Doppler effect increases or decreases the pitch of sounds - relative to the velocity of the source and the listener. \ No newline at end of file + This setting controls the strength of the Doppler effect. The Doppler effect increases or decreases the pitch of sounds + relative to the velocity of the sound source and the listener. diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index c7b22be633..ec6372584b 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -1448,7 +1448,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Controls the strength of the doppler effect. Zero means it is completely disabled. + <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> diff --git a/files/lang/launcher_en.ts b/files/lang/launcher_en.ts index 7c29749b15..d510ec5b2b 100644 --- a/files/lang/launcher_en.ts +++ b/files/lang/launcher_en.ts @@ -1448,7 +1448,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Controls the strength of the doppler effect. Zero means it is completely disabled. + <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index f81c3d78d5..eb056c02d6 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -1451,7 +1451,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Script à lancer après démarrage : - Controls the strength of the doppler effect. Zero means it is completely disabled. + <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 71bbbf4f4a..5af50f3fea 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1463,12 +1463,12 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Использовать камеру как слушателя - Controls the strength of the doppler effect. Zero means it is completely disabled. + <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> Doppler Factor - + Множитель эффекта Доплера diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts index ca822f55b8..db3b64521f 100644 --- a/files/lang/launcher_sv.ts +++ b/files/lang/launcher_sv.ts @@ -1467,7 +1467,7 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin <html><head/><body><p>Vid aktivering gör denna funktion att övergångarna mellan olika animationer och poser blir mycket mjukare. Funktionen gör det också möjligt att konfigurera animationsövergångarna i YAML-filer. Dessa filer kan buntas ihop tillsammans med nya animationsfiler.</p></body></html> - Controls the strength of the doppler effect. Zero means it is completely disabled. + <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> From ee540039a1d6e866d00a4afe708a6b167af68172 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 22 May 2025 20:54:23 +0200 Subject: [PATCH 27/55] Address string_view conversions flagged by SonarQube --- apps/openmw/mwscript/miscextensions.cpp | 2 +- components/l10n/manager.cpp | 12 +-- components/l10n/manager.hpp | 10 +-- components/l10n/messagebundles.cpp | 100 ++++++++++++------------ components/l10n/messagebundles.hpp | 13 ++- components/lua/l10n.cpp | 2 +- 6 files changed, 74 insertions(+), 65 deletions(-) diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index af81fd2d3c..fd2064463e 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -615,7 +615,7 @@ namespace MWScript long key; - if (const auto k = ::Misc::StringUtils::toNumeric(effect.data()); + if (const auto k = ::Misc::StringUtils::toNumeric(effect); k.has_value() && *k >= 0 && *k <= 32767) key = *k; else diff --git a/components/l10n/manager.cpp b/components/l10n/manager.cpp index 27a4d603d4..b176e4aa7a 100644 --- a/components/l10n/manager.cpp +++ b/components/l10n/manager.cpp @@ -14,7 +14,7 @@ namespace L10n mPreferredLocales.clear(); if (gmstHasPriority) mPreferredLocales.push_back(icu::Locale("gmst")); - std::set langSet; + std::set> langSet; for (const auto& lang : langs) { if (langSet.contains(lang)) @@ -34,7 +34,7 @@ namespace L10n updateContext(key.first, *context); } - void Manager::readLangData(const std::string& name, MessageBundles& ctx, const icu::Locale& lang) + void Manager::readLangData(std::string_view name, MessageBundles& ctx, const icu::Locale& lang) { std::string langName(lang.getName()); langName += ".yaml"; @@ -58,7 +58,7 @@ namespace L10n } } - void Manager::updateContext(const std::string& name, MessageBundles& ctx) + void Manager::updateContext(std::string_view name, MessageBundles& ctx) { icu::Locale fallbackLocale = ctx.getFallbackLocale(); ctx.setPreferredLocales(mPreferredLocales); @@ -89,9 +89,9 @@ namespace L10n } std::shared_ptr Manager::getContext( - const std::string& contextName, const std::string& fallbackLocaleName) + std::string_view contextName, const std::string& fallbackLocaleName) { - std::pair key(contextName, fallbackLocaleName); + std::pair key(contextName, fallbackLocaleName); auto it = mCache.find(key); if (it != mCache.end()) return it->second; @@ -102,7 +102,7 @@ namespace L10n for (char c : contextName) valid = valid && allowedChar(c); if (!valid) - throw std::runtime_error(std::string("Invalid l10n context name: ") + contextName); + throw std::runtime_error("Invalid l10n context name: " + std::string(contextName)); icu::Locale fallbackLocale(fallbackLocaleName.c_str()); std::shared_ptr ctx = std::make_shared(mPreferredLocales, fallbackLocale); ctx->setGmstLoader(mGmstLoader); diff --git a/components/l10n/manager.hpp b/components/l10n/manager.hpp index 4b047fa9d7..7022057178 100644 --- a/components/l10n/manager.hpp +++ b/components/l10n/manager.hpp @@ -27,20 +27,20 @@ namespace L10n void setGmstLoader(std::function fn) { mGmstLoader = std::move(fn); } std::shared_ptr getContext( - const std::string& contextName, const std::string& fallbackLocale = "en"); + std::string_view contextName, const std::string& fallbackLocale = "en"); - std::string getMessage(const std::string& contextName, std::string_view key) + std::string getMessage(std::string_view contextName, std::string_view key) { return getContext(contextName)->formatMessage(key, {}, {}); } private: - void readLangData(const std::string& name, MessageBundles& ctx, const icu::Locale& lang); - void updateContext(const std::string& name, MessageBundles& ctx); + void readLangData(std::string_view name, MessageBundles& ctx, const icu::Locale& lang); + void updateContext(std::string_view name, MessageBundles& ctx); const VFS::Manager* mVFS; std::vector mPreferredLocales; - std::map, std::shared_ptr> mCache; + std::map, std::shared_ptr, std::less<>> mCache; std::function mGmstLoader; }; diff --git a/components/l10n/messagebundles.cpp b/components/l10n/messagebundles.cpp index e8a56a9bb3..665704e30d 100644 --- a/components/l10n/messagebundles.cpp +++ b/components/l10n/messagebundles.cpp @@ -9,6 +9,50 @@ namespace L10n { + namespace + { + std::string getErrorText(const UParseError& parseError) + { + icu::UnicodeString preContext(parseError.preContext), postContext(parseError.postContext); + std::string parseErrorString; + preContext.toUTF8String(parseErrorString); + postContext.toUTF8String(parseErrorString); + return parseErrorString; + } + + template + bool checkSuccess(const icu::ErrorCode& status, const UParseError& parseError, Args const&... message) + { + if (status.isFailure()) + { + std::string errorText = getErrorText(parseError); + if (!errorText.empty()) + { + (Log(Debug::Error) << ... << message) + << ": " << status.errorName() << " in \"" << errorText << "\""; + } + else + { + (Log(Debug::Error) << ... << message) << ": " << status.errorName(); + } + } + return status.isSuccess(); + } + + std::string loadGmst( + const std::function& gmstLoader, const icu::MessageFormat* message) + { + icu::UnicodeString gmstNameUnicode; + std::string gmstName; + icu::ErrorCode success; + message->format(nullptr, nullptr, 0, gmstNameUnicode, success); + gmstNameUnicode.toUTF8String(gmstName); + if (gmstLoader) + return gmstLoader(gmstName); + return "GMST:" + gmstName; + } + } + MessageBundles::MessageBundles(const std::vector& preferredLocales, icu::Locale& fallbackLocale) : mFallbackLocale(fallbackLocale) { @@ -39,33 +83,6 @@ namespace L10n } } - std::string getErrorText(const UParseError& parseError) - { - icu::UnicodeString preContext(parseError.preContext), postContext(parseError.postContext); - std::string parseErrorString; - preContext.toUTF8String(parseErrorString); - postContext.toUTF8String(parseErrorString); - return parseErrorString; - } - - static bool checkSuccess( - const icu::ErrorCode& status, const std::string& message, const UParseError parseError = UParseError()) - { - if (status.isFailure()) - { - std::string errorText = getErrorText(parseError); - if (!errorText.empty()) - { - Log(Debug::Error) << message << ": " << status.errorName() << " in \"" << errorText << "\""; - } - else - { - Log(Debug::Error) << message << ": " << status.errorName(); - } - } - return status.isSuccess(); - } - void MessageBundles::load(std::istream& input, const icu::Locale& lang) { YAML::Node data = YAML::Load(input); @@ -80,20 +97,19 @@ namespace L10n icu::ErrorCode status; UParseError parseError; icu::MessageFormat message(pattern, langOrEn, parseError, status); - if (checkSuccess(status, std::string("Failed to create message ") + key + " for locale " + lang.getName(), - parseError)) + if (checkSuccess(status, parseError, "Failed to create message ", key, " for locale ", lang.getName())) { - mBundles[localeName].insert(std::make_pair(key, message)); + mBundles[localeName].emplace(key, message); } } } - const icu::MessageFormat* MessageBundles::findMessage(std::string_view key, const std::string& localeName) const + const icu::MessageFormat* MessageBundles::findMessage(std::string_view key, std::string_view localeName) const { auto iter = mBundles.find(localeName); if (iter != mBundles.end()) { - auto message = iter->second.find(key.data()); + auto message = iter->second.find(key); if (message != iter->second.end()) { return &(message->second); @@ -116,20 +132,6 @@ namespace L10n return formatMessage(key, argNames, argValues); } - static std::string loadGmst( - const std::function gmstLoader, const icu::MessageFormat* message) - { - icu::UnicodeString gmstNameUnicode; - std::string gmstName; - icu::ErrorCode success; - message->format(nullptr, nullptr, 0, gmstNameUnicode, success); - gmstNameUnicode.toUTF8String(gmstName); - if (gmstLoader) - return gmstLoader(gmstName); - else - return "GMST:" + gmstName; - } - std::string MessageBundles::formatMessage(std::string_view key, const std::vector& argNames, const std::vector& args) const { @@ -158,7 +160,7 @@ namespace L10n message->format(argNames.data(), args.data(), static_cast(args.size()), result, success); else message->format(nullptr, nullptr, static_cast(args.size()), result, success); - checkSuccess(success, std::string("Failed to format message ") + key.data()); + checkSuccess(success, {}, "Failed to format message ", key); result.toUTF8String(resultString); return resultString; } @@ -171,7 +173,7 @@ namespace L10n icu::MessageFormat defaultMessage( icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), static_cast(key.size()))), defaultLocale, parseError, success); - if (!checkSuccess(success, std::string("Failed to create message ") + key.data(), parseError)) + if (!checkSuccess(success, parseError, "Failed to create message ", key)) // If we can't parse the key as a pattern, just return the key return std::string(key); @@ -180,7 +182,7 @@ namespace L10n argNames.data(), args.data(), static_cast(args.size()), result, success); else defaultMessage.format(nullptr, nullptr, static_cast(args.size()), result, success); - checkSuccess(success, std::string("Failed to format message ") + key.data()); + checkSuccess(success, {}, "Failed to format message ", key); result.toUTF8String(resultString); return resultString; } diff --git a/components/l10n/messagebundles.hpp b/components/l10n/messagebundles.hpp index f142d3f2ac..15ccbaa630 100644 --- a/components/l10n/messagebundles.hpp +++ b/components/l10n/messagebundles.hpp @@ -10,6 +10,8 @@ #include #include +#include + namespace L10n { /** @@ -41,18 +43,23 @@ namespace L10n void setPreferredLocales(const std::vector& preferredLocales); const std::vector& getPreferredLocales() const { return mPreferredLocales; } void load(std::istream& input, const icu::Locale& lang); - bool isLoaded(const icu::Locale& loc) const { return mBundles.find(loc.getName()) != mBundles.end(); } + bool isLoaded(const icu::Locale& loc) const + { + return mBundles.find(std::string_view(loc.getName())) != mBundles.end(); + } const icu::Locale& getFallbackLocale() const { return mFallbackLocale; } void setGmstLoader(std::function fn) { mGmstLoader = std::move(fn); } private: + template + using StringMap = std::unordered_map>; // icu::Locale isn't hashable (or comparable), so we use the string form instead, which is canonicalized - std::unordered_map> mBundles; + StringMap> mBundles; const icu::Locale mFallbackLocale; std::vector mPreferredLocaleStrings; std::vector mPreferredLocales; std::function mGmstLoader; - const icu::MessageFormat* findMessage(std::string_view key, const std::string& localeName) const; + const icu::MessageFormat* findMessage(std::string_view key, std::string_view localeName) const; }; } diff --git a/components/lua/l10n.cpp b/components/lua/l10n.cpp index 8153efd5b5..856ab8a808 100644 --- a/components/lua/l10n.cpp +++ b/components/lua/l10n.cpp @@ -66,7 +66,7 @@ namespace LuaUtil }; return sol::make_object( - lua, [manager](const std::string& contextName, sol::optional fallbackLocale) { + lua, [manager](std::string_view contextName, sol::optional fallbackLocale) { if (fallbackLocale) return L10nContext{ manager->getContext(contextName, *fallbackLocale) }; else From 75845bf8633998532beeadf61573919b02db0003 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 30 Jul 2025 19:41:14 +0200 Subject: [PATCH 28/55] Work around compiler versions that haven't addressed defect 3865 --- components/l10n/manager.cpp | 4 ++-- components/l10n/manager.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/l10n/manager.cpp b/components/l10n/manager.cpp index b176e4aa7a..1f75c5b073 100644 --- a/components/l10n/manager.cpp +++ b/components/l10n/manager.cpp @@ -31,7 +31,7 @@ namespace L10n msg << " " << l.getName(); } for (auto& [key, context] : mCache) - updateContext(key.first, *context); + updateContext(std::get<0>(key), *context); } void Manager::readLangData(std::string_view name, MessageBundles& ctx, const icu::Locale& lang) @@ -91,7 +91,7 @@ namespace L10n std::shared_ptr Manager::getContext( std::string_view contextName, const std::string& fallbackLocaleName) { - std::pair key(contextName, fallbackLocaleName); + std::tuple key(contextName, fallbackLocaleName); auto it = mCache.find(key); if (it != mCache.end()) return it->second; diff --git a/components/l10n/manager.hpp b/components/l10n/manager.hpp index 7022057178..89a9bd4b05 100644 --- a/components/l10n/manager.hpp +++ b/components/l10n/manager.hpp @@ -40,7 +40,7 @@ namespace L10n const VFS::Manager* mVFS; std::vector mPreferredLocales; - std::map, std::shared_ptr, std::less<>> mCache; + std::map, std::shared_ptr, std::less<>> mCache; std::function mGmstLoader; }; From bc05628fa8b93da907052c9d620d08d25873f4f8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 30 Jul 2025 20:44:59 +0200 Subject: [PATCH 29/55] Resolve unused code warnings when compiling in Debug mode using MSVC --- apps/components_tests/lua/testscriptscontainer.cpp | 3 ++- apps/esmtool/esmtool.cpp | 2 -- apps/openmw/mwrender/animation.cpp | 4 ++-- components/fx/technique.cpp | 12 ++++++------ components/fx/types.hpp | 12 ++++++------ components/lua/scriptscontainer.cpp | 5 +++-- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/components_tests/lua/testscriptscontainer.cpp b/apps/components_tests/lua/testscriptscontainer.cpp index 4f3cca1b87..9c4c656b32 100644 --- a/apps/components_tests/lua/testscriptscontainer.cpp +++ b/apps/components_tests/lua/testscriptscontainer.cpp @@ -638,8 +638,9 @@ CUSTOM: customdata.lua sol::object deserialized = LuaUtil::deserialize(lua.sol(), data2.mScripts[0].mData, &serializer1); EXPECT_TRUE(deserialized.is()); sol::table table = deserialized; - for (const auto& [key, value] : table) + if (!table.empty()) { + const auto [key, value] = *table.cbegin(); EXPECT_TRUE(key.is()); EXPECT_TRUE(value.is()); EXPECT_EQ(key.as(), (ESM::RefNum{ 42, 34 })); diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 0473676f93..7cdc2bcd98 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -215,8 +215,6 @@ int main(int argc, char** argv) std::cerr << "ERROR: " << e.what() << std::endl; return 1; } - - return 0; } namespace diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index d22e12ff01..5912895855 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1117,8 +1117,8 @@ namespace MWRender return keyframeController->getAsCallback(); } - - return asCallback; + else + return asCallback; } void Animation::resetActiveGroups() diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index 5865298fe5..a88a1a62bb 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -528,8 +528,6 @@ namespace Fx { return parseBool(); } - - error(Misc::StringUtils::format("failed setting uniform type")); } template @@ -557,10 +555,12 @@ namespace Fx { if constexpr (std::is_same_v) error("bool arrays currently unsupported"); - - int size = parseInteger(); - if (size > 1) - data.mArray = std::vector(size); + else + { + int size = parseInteger(); + if (size > 1) + data.mArray = std::vector(size); + } } else if (key == "min") { diff --git a/components/fx/types.hpp b/components/fx/types.hpp index ddfa966010..440bc69470 100644 --- a/components/fx/types.hpp +++ b/components/fx/types.hpp @@ -220,10 +220,11 @@ namespace Fx return osg::Uniform::FLOAT; else if constexpr (std::is_same_v) return osg::Uniform::INT; - else if constexpr (std::is_same_v) + else + { + static_assert(std::is_same_v, "Non-exhaustive visitor"); return osg::Uniform::BOOL; - - return std::nullopt; + } }, mData); } @@ -293,15 +294,14 @@ namespace Fx return Misc::StringUtils::format("const int %s=%i;", mName, value); } - else if constexpr (std::is_same_v) + else { + static_assert(std::is_same_v, "Non-exhaustive visitor"); if (useUniform) return Misc::StringUtils::format("uniform bool %s;", uname); return Misc::StringUtils::format("const bool %s=%s;", mName, value ? "true" : "false"); } - - return std::nullopt; }, mData); } diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 5eff211894..cdeb528205 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -215,12 +215,13 @@ namespace LuaUtil return true; } } + return false; } - else if constexpr (std::is_same_v) + else { + static_assert(std::is_same_v, "Non-exhaustive visitor"); return variant.mScripts.count(scriptId) != 0; } - return false; }, mData); } From aae21c6f53f64d83e0a68311dc454455d8b08322 Mon Sep 17 00:00:00 2001 From: epochwon Date: Wed, 30 Jul 2025 14:51:18 -0400 Subject: [PATCH 30/55] Get physics fps delta time from physics system --- apps/openmw/mwbase/world.hpp | 2 ++ apps/openmw/mwphysics/physicssystem.hpp | 4 ++-- apps/openmw/mwsound/openaloutput.cpp | 2 -- apps/openmw/mwsound/soundmanagerimp.cpp | 17 +++++------------ apps/openmw/mwworld/worldimp.cpp | 5 +++++ apps/openmw/mwworld/worldimp.hpp | 2 ++ 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f268ed0e52..9b2755ac8a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -481,6 +481,8 @@ namespace MWBase virtual float getSunVisibility() const = 0; virtual float getSunPercentage() const = 0; + virtual float getPhysicsFrameRateDT() const = 0; + virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0; /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 546d72676e..e85fc23067 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -287,6 +287,8 @@ namespace MWPhysics void reportStats(unsigned int frameNumber, osg::Stats& stats) const; void reportCollision(const btVector3& position, const btVector3& normal); + float mPhysicsDt; + private: void updateWater(); @@ -330,8 +332,6 @@ namespace MWPhysics osg::ref_ptr mParentNode; - float mPhysicsDt; - std::size_t mSimulationsCounter = 0; std::array, 2> mSimulations; std::vector> mActorsPositions; diff --git a/apps/openmw/mwsound/openaloutput.cpp b/apps/openmw/mwsound/openaloutput.cpp index 58f78ac280..ed6e448645 100644 --- a/apps/openmw/mwsound/openaloutput.cpp +++ b/apps/openmw/mwsound/openaloutput.cpp @@ -1587,8 +1587,6 @@ namespace MWSound : SoundOutput(mgr) , mDevice(nullptr) , mContext(nullptr) - , mListenerPos(0.0f, 0.0f, 0.0f) - , mListenerVel(0.0f, 0.0f, 0.0f) , mListenerEnv(Env_Normal) , mWaterFilter(0) , mWaterEffect(0) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 3933af1523..de285e4b3b 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -43,8 +42,6 @@ namespace MWSound constexpr float sSfxFadeOutDuration = 1.0f; constexpr float sSoundCullDistance = 2000.f; - float physicsFramerate = 60.f; - WaterSoundUpdaterSettings makeWaterSoundUpdaterSettings() { WaterSoundUpdaterSettings settings; @@ -142,13 +139,6 @@ namespace MWSound return; } - if (const char* env = getenv("OPENMW_PHYSICS_FPS")) - { - if (const auto physFramerate = Misc::StringUtils::toNumeric(env); - physFramerate.has_value() && *physFramerate > 0) - physicsFramerate = *physFramerate; - } - std::vector names = mOutput->enumerate(); std::stringstream stream; @@ -991,7 +981,9 @@ namespace MWSound { sound->setLastPosition(sound->getPosition()); sound->setPosition(ptr.getRefData().getPosition().asVec3()); - sound->setVelocity((sound->getPosition() - sound->getLastPosition()) * physicsFramerate); + MWBase::World* world = MWBase::Environment::get().getWorld(); + sound->setVelocity( + (sound->getPosition() - sound->getLastPosition()) / world->getPhysicsFrameRateDT()); } cull3DSound(sound); @@ -1031,7 +1023,8 @@ namespace MWSound sound->setLastPosition(sound->getPosition()); MWBase::World* world = MWBase::Environment::get().getWorld(); sound->setPosition(world->getActorHeadTransform(ptr).getTrans()); - sound->setVelocity((sound->getPosition() - sound->getLastPosition()) * physicsFramerate); + sound->setVelocity( + (sound->getPosition() - sound->getLastPosition()) / world->getPhysicsFrameRateDT()); } cull3DSound(sound); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index eaa8799e84..1ed18a25af 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3152,6 +3152,11 @@ namespace MWWorld return mWeatherManager->getSunPercentage(getTimeStamp().getHour()); } + float World::getPhysicsFrameRateDT() const + { + return mPhysics->mPhysicsDt; + } + bool World::findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) { if (cell->isExterior()) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index b1286d5532..c0a7c37b8f 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -576,6 +576,8 @@ namespace MWWorld float getSunVisibility() const override; float getSunPercentage() const override; + float getPhysicsFrameRateDT() const override; + bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) override; /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) From b792bc0ee2577ff5a3b9afe4e83bcadc81edc521 Mon Sep 17 00:00:00 2001 From: epochwon Date: Thu, 31 Jul 2025 11:14:08 -0400 Subject: [PATCH 31/55] fixes + russian line --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 8 ++++---- apps/openmw/mwsound/soundmanagerimp.cpp | 4 ++-- apps/openmw/mwworld/worldimp.cpp | 2 +- apps/openmw/mwworld/worldimp.hpp | 2 +- files/lang/launcher_ru.ts | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 9b2755ac8a..88255e754c 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -481,7 +481,7 @@ namespace MWBase virtual float getSunVisibility() const = 0; virtual float getSunPercentage() const = 0; - virtual float getPhysicsFrameRateDT() const = 0; + virtual float getPhysicsFrameRateDt() const = 0; virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5e7c70788d..2571448da2 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -93,9 +93,10 @@ namespace namespace MWPhysics { PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) - : mShapeManager( - std::make_unique(resourceSystem->getVFS(), resourceSystem->getSceneManager(), - resourceSystem->getNifFileManager(), Settings::cells().mCacheExpiryDelay)) + : mPhysicsDt(1.f / 60.f) + , mShapeManager(std::make_unique(resourceSystem->getVFS(), + resourceSystem->getSceneManager(), resourceSystem->getNifFileManager(), + Settings::cells().mCacheExpiryDelay)) , mResourceSystem(resourceSystem) , mDebugDrawEnabled(false) , mTimeAccum(0.0f) @@ -103,7 +104,6 @@ namespace MWPhysics , mWaterHeight(0) , mWaterEnabled(false) , mParentNode(std::move(parentNode)) - , mPhysicsDt(1.f / 60.f) { mResourceSystem->addResourceManager(mShapeManager.get()); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index de285e4b3b..86631448a9 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -983,7 +983,7 @@ namespace MWSound sound->setPosition(ptr.getRefData().getPosition().asVec3()); MWBase::World* world = MWBase::Environment::get().getWorld(); sound->setVelocity( - (sound->getPosition() - sound->getLastPosition()) / world->getPhysicsFrameRateDT()); + (sound->getPosition() - sound->getLastPosition()) / world->getPhysicsFrameRateDt()); } cull3DSound(sound); @@ -1024,7 +1024,7 @@ namespace MWSound MWBase::World* world = MWBase::Environment::get().getWorld(); sound->setPosition(world->getActorHeadTransform(ptr).getTrans()); sound->setVelocity( - (sound->getPosition() - sound->getLastPosition()) / world->getPhysicsFrameRateDT()); + (sound->getPosition() - sound->getLastPosition()) / world->getPhysicsFrameRateDt()); } cull3DSound(sound); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1ed18a25af..2e858324ec 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3152,7 +3152,7 @@ namespace MWWorld return mWeatherManager->getSunPercentage(getTimeStamp().getHour()); } - float World::getPhysicsFrameRateDT() const + float World::getPhysicsFrameRateDt() const { return mPhysics->mPhysicsDt; } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index c0a7c37b8f..ed41ed8263 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -576,7 +576,7 @@ namespace MWWorld float getSunVisibility() const override; float getSunPercentage() const override; - float getPhysicsFrameRateDT() const override; + float getPhysicsFrameRateDt() const override; bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) override; diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 5af50f3fea..f3c00360b4 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1464,7 +1464,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> - + <html><head/><body><p>Определяет силу эффекта Доплера. Нулевое значение означает, что эффект отключен полностью.</p><p>Эффект Доплера увеличивает или уменьшает высоту звуков в зависимости от скорости источника звука и слушателя.</p></body></html> Doppler Factor From 645eb811331b7d8337afb8dbf4f1b6314b26b6a0 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 21 Dec 2024 11:01:39 +0400 Subject: [PATCH 32/55] Run onUpdate when the game is paused --- CMakeLists.txt | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 13 ++++++------- .../reference/lua-scripting/engine_handlers.rst | 2 +- files/data/scripts/omw/camera/camera.lua | 4 ++++ .../scripts/omw/mechanics/animationcontroller.lua | 4 ++++ .../data/scripts/omw/mechanics/playercontroller.lua | 6 +++++- files/data/scripts/omw/music/actor.lua | 6 +++--- files/lua_api/openmw/core.lua | 2 +- files/lua_api/openmw/world.lua | 2 +- 9 files changed, 26 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d8e3cade9..b4ef872eb6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 50) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 85) +set(OPENMW_LUA_API_REVISION 86) set(OPENMW_POSTPROCESSING_API_REVISION 3) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 9c2778e55d..df9c9cd50f 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -212,13 +212,12 @@ namespace MWLua // Run engine handlers mEngineEvents.callEngineHandlers(); - if (!timeManager.isPaused()) - { - float frameDuration = MWBase::Environment::get().getFrameDuration(); - for (LocalScripts* scripts : mActiveLocalScripts) - scripts->update(frameDuration); - mGlobalScripts.update(frameDuration); - } + bool isPaused = timeManager.isPaused(); + + float frameDuration = MWBase::Environment::get().getFrameDuration(); + for (LocalScripts* scripts : mActiveLocalScripts) + scripts->update(isPaused ? 0 : frameDuration); + mGlobalScripts.update(isPaused ? 0 : frameDuration); mLua.protectedCall([&](LuaUtil::LuaView& lua) { mScriptTracker.unloadInactiveScripts(lua); }); } diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 29b14aee55..cc1e45403f 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -28,7 +28,7 @@ Engine handler is a function defined by a script, that can be called by the engi | `assigned to a script in openmw-cs (not yet implemented).` | ``onInterfaceOverride`` can be called before ``onInit``. * - onUpdate(dt) - - | Called every frame if the game is not paused. `dt` is + - | Called every frame in the Lua thread (even if the game is paused). `dt` is | the simulation time from the last update in seconds. * - onSave() -> savedData - | Called when the game is saving. May be called in inactive state, diff --git a/files/data/scripts/omw/camera/camera.lua b/files/data/scripts/omw/camera/camera.lua index 6730e0c069..52b64906ea 100644 --- a/files/data/scripts/omw/camera/camera.lua +++ b/files/data/scripts/omw/camera/camera.lua @@ -182,6 +182,10 @@ local function updateCrosshair() end local function onUpdate(dt) + if dt <= 0 then + return + end + camera.setExtraPitch(0) camera.setExtraYaw(0) camera.setExtraRoll(0) diff --git a/files/data/scripts/omw/mechanics/animationcontroller.lua b/files/data/scripts/omw/mechanics/animationcontroller.lua index 91cb60d177..9edc7565ca 100644 --- a/files/data/scripts/omw/mechanics/animationcontroller.lua +++ b/files/data/scripts/omw/mechanics/animationcontroller.lua @@ -41,6 +41,10 @@ end local initialized = false local function onUpdate(dt) + if dt <= 0 then + return + end + -- The script is loaded before the actor's CharacterController object is initialized, therefore -- we have to delay this initialization step or the call won't have any effect. if not initialized then diff --git a/files/data/scripts/omw/mechanics/playercontroller.lua b/files/data/scripts/omw/mechanics/playercontroller.lua index 8b4d618917..6de31afdea 100644 --- a/files/data/scripts/omw/mechanics/playercontroller.lua +++ b/files/data/scripts/omw/mechanics/playercontroller.lua @@ -96,7 +96,11 @@ local function skillUsedHandler(skillid, params) end end -local function onUpdate() +local function onUpdate(dt) + if dt <= 0 then + return + end + if self.cell ~= cell then cell = self.cell onCellChange() diff --git a/files/data/scripts/omw/music/actor.lua b/files/data/scripts/omw/music/actor.lua index 8f3ac7915a..02cded7904 100755 --- a/files/data/scripts/omw/music/actor.lua +++ b/files/data/scripts/omw/music/actor.lua @@ -11,7 +11,7 @@ local function emitTargetsChanged() end end -local function onUpdate() +local function onUpdate(dt) if types.Actor.isDeathFinished(self) or not types.Actor.isInActorsProcessingRange(self) then if next(targets) ~= nil then targets = {} @@ -21,10 +21,10 @@ local function onUpdate() return end - -- Early-out for actors without targets and without combat state + -- Early-out for actors without targets and without combat state when the game is not paused -- 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 and not AI.isFleeing() then + if isStanceNothing and next(targets) == nil and not AI.isFleeing() and dt > 0 then return end diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 0567cec859..054e96674b 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -42,7 +42,7 @@ -- @return #number --- --- Whether the world is paused (onUpdate doesn't work when the world is paused). +-- Whether the world is paused. -- @function [parent=#core] isWorldPaused -- @return #boolean diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index ae494f8c99..22126ce8f4 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -119,7 +119,7 @@ -- @param #number ratio --- --- Whether the world is paused (onUpdate doesn't work when the world is paused). +-- Whether the world is paused. -- @function [parent=#world] isWorldPaused -- @return #boolean From 0801e0512d8a0d93fe278998c3e5ed1365b2249e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 27 Jul 2025 18:15:19 +0200 Subject: [PATCH 33/55] Do thread safe local static based initialization --- apps/openmw/mwgui/mapwindow.cpp | 8 +++----- apps/openmw/mwrender/npcanimation.cpp | 8 +++----- apps/openmw/mwrender/ripples.cpp | 3 +-- apps/openmw/mwsound/ffmpegdecoder.cpp | 8 +++----- apps/openmw/mwworld/scene.cpp | 3 +-- components/sceneutil/depth.hpp | 9 +++------ 6 files changed, 14 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 59d21886dc..51a765442a 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -774,12 +774,10 @@ namespace MWGui , mGlobalMapRender(std::make_unique(localMapRender->getRoot(), workQueue)) , mEditNoteDialog() { - static bool registered = false; - if (!registered) - { + [[maybe_unused]] static const bool registered = [] { MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - registered = true; - } + return true; + }(); mEditNoteDialog.setVisible(false); mEditNoteDialog.eventOkClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditOk); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index c14eee27e4..4aface83d6 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -401,14 +401,12 @@ namespace MWRender { if (mViewMode == VM_FirstPerson) { - static bool prototypeAdded = false; - if (!prototypeAdded) - { + [[maybe_unused]] static const bool prototypeAdded = [&] { osg::ref_ptr depthClearBin(new osgUtil::RenderBin); depthClearBin->setDrawCallback(new DepthClearCallback()); osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); - prototypeAdded = true; - } + return true; + }(); mObjectRoot->getOrCreateStateSet()->setRenderBinDetails( RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); } diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index ab982d0c55..b19863d695 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -88,14 +88,13 @@ namespace MWRender if (mProgramBlobber != nullptr) { - static bool pipelineLogged = [&] { + [[maybe_unused]] static const bool pipelineLogged = [&] { if (mUseCompute) Log(Debug::Info) << "Initialized compute shader pipeline for water ripples"; else Log(Debug::Info) << "Initialized fallback fragment shader pipeline for water ripples"; return true; }(); - (void)pipelineLogged; } setCullCallback(new osg::NodeCallback); diff --git a/apps/openmw/mwsound/ffmpegdecoder.cpp b/apps/openmw/mwsound/ffmpegdecoder.cpp index 5a0f336a93..f8b40ddabb 100644 --- a/apps/openmw/mwsound/ffmpegdecoder.cpp +++ b/apps/openmw/mwsound/ffmpegdecoder.cpp @@ -522,16 +522,14 @@ namespace MWSound /* We need to make sure ffmpeg is initialized. Optionally silence warning * output from the lib */ - static bool done_init = false; - if (!done_init) - { + [[maybe_unused]] static const bool doneInit = [] { // This is not needed anymore above FFMpeg version 4.0 #if LIBAVCODEC_VERSION_INT < 3805796 av_register_all(); #endif av_log_set_level(AV_LOG_ERROR); - done_init = true; - } + return true; + }(); } FFmpegDecoder::~FFmpegDecoder() diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 0c9a13bc47..1a68142c15 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1273,13 +1273,12 @@ namespace MWWorld const std::size_t leftCapacity = mPreloader->getMaxCacheSize() - mPreloader->getCacheSize(); if (cells.size() > leftCapacity) { - static bool logged = [&] { + [[maybe_unused]] static const bool logged = [&] { Log(Debug::Warning) << "Not enough cell preloader cache capacity to preload exterior cells, consider " "increasing \"preload cell cache max\" up to " << (mPreloader->getCacheSize() + cells.size()); return true; }(); - (void)logged; cells.resize(leftCapacity); } diff --git a/components/sceneutil/depth.hpp b/components/sceneutil/depth.hpp index a9f99b145f..b9004c457f 100644 --- a/components/sceneutil/depth.hpp +++ b/components/sceneutil/depth.hpp @@ -95,13 +95,10 @@ namespace SceneUtil static void setReversed(bool reverseZ) { - static bool init = false; - - if (!init) - { + [[maybe_unused]] static const bool init = [&] { AutoDepth::sReversed = reverseZ; - init = true; - } + return true; + }(); } static bool isReversed() From 929a65126aad0dcca243040112feaba177e1ffe0 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 1 Aug 2025 08:25:02 -0700 Subject: [PATCH 34/55] Fix docs --- files/lua_api/openmw/ambient.lua | 4 ++-- files/lua_api/openmw/animation.lua | 6 +++--- files/lua_api/openmw/camera.lua | 5 ++--- files/lua_api/openmw/core.lua | 6 +++--- files/lua_api/openmw/types.lua | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/files/lua_api/openmw/ambient.lua b/files/lua_api/openmw/ambient.lua index 436f10de1a..a9e8c08d4a 100644 --- a/files/lua_api/openmw/ambient.lua +++ b/files/lua_api/openmw/ambient.lua @@ -18,7 +18,7 @@ -- * `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 +-- timeOffset=0.1, -- volume=0.3, -- scale=false, -- pitch=1.0, @@ -38,7 +38,7 @@ -- * `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 +-- timeOffset=0.1, -- volume=0.3, -- scale=false, -- pitch=1.0, diff --git a/files/lua_api/openmw/animation.lua b/files/lua_api/openmw/animation.lua index 929a784ba9..b182a1e0af 100644 --- a/files/lua_api/openmw/animation.lua +++ b/files/lua_api/openmw/animation.lua @@ -188,7 +188,7 @@ -- -- * `loops` - a number >= 0, the number of times the animation should loop after the first play (default: 0). -- * `priority` - Either a single #Priority value that will be assigned to all bone groups. Or a table mapping bone groups to its priority (default: PRIORITY.Default). --- * `blendMask` - A mask of which bone groups to include in the animation (Default: BLEND_MASK.All. +-- * `blendMask` - A mask of which bone groups to include in the animation (Default: BLEND_MASK.All). -- * `autoDisable` - If true, the animation will be immediately removed upon finishing, which means information will not be possible to query once completed. (Default: true) -- * `speed` - a floating point number >= 0, the speed at which the animation should play (default: 1) -- * `startKey` - the animation key at which the animation should start (default: "start") @@ -242,7 +242,7 @@ -- model = types.Static.record(mgef.hitStatic).model, -- options = { -- vfxId = mgef.id, --- particuleTextureOverride = mgef.particle, +-- particleTextureOverride = mgef.particle, -- loop = false, -- } -- }) @@ -254,7 +254,7 @@ -- Can only be used on self. -- @function [parent=#animation] removeVfx -- @param openmw.core#GameObject actor --- @param #number vfxId an integer ID that uniquely identifies the VFX to remove +-- @param #string vfxId an integer ID that uniquely identifies the VFX to remove --- -- Removes all vfx from the actor. diff --git a/files/lua_api/openmw/camera.lua b/files/lua_api/openmw/camera.lua index b6b605fc10..67b3ec2a8e 100644 --- a/files/lua_api/openmw/camera.lua +++ b/files/lua_api/openmw/camera.lua @@ -109,7 +109,6 @@ --- -- Additional summand for the yaw angle; useful for camera shaking effects. --- Setting extra pitch doesn't block player input. -- Full yaw is `getYaw()+getExtraYaw()`. -- @function [parent=#camera] setExtraYaw -- @param #number value @@ -122,7 +121,7 @@ --- -- Additional summand for the roll angle; useful for camera shaking effects. --- Full yaw is `getRoll()+getExtraRoll()`. +-- Full roll is `getRoll()+getExtraRoll()`. -- @function [parent=#camera] setExtraRoll -- @param #number value @@ -150,7 +149,7 @@ --- -- Set preferred offset between tracked position (see `getTrackedPosition`) and the camera focal point (the center of the screen) in third person mode. -- The offset is a 2d vector (X, Y) where X is horizontal (to the right from the character) and Y component is vertical (upward). --- The real offset can differ from the preferred one during smooth transition of if blocked by an obstacle. +-- The real offset can differ from the preferred one during smooth transition or if blocked by an obstacle. -- Smooth transition happens by default every time when the preferred offset was changed. Use `instantTransition()` to skip the current transition. -- @function [parent=#camera] setFocalPreferredOffset -- @param openmw.util#Vector2 offset diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 054e96674b..43bb22058b 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -167,7 +167,7 @@ -- @field openmw.util#Transform startingRotation The object original rotation -- @field #ObjectOwner owner Ownership information -- @field #Cell cell The cell where the object currently is. During loading a game and for objects in an inventory or a container `cell` is nil. --- @field #GameObject parentContainer Container or actor that contains (or has in inventory) this object. It is nil if the object is in a cell. +-- @field #GameObject parentContainer Container or actor that contains this object (or has in inventory) . It is nil if the object is in a cell. -- @field #any type Type of the object (one of the tables from the package @{openmw.types#types}). -- @field #number count Count (>1 means a stack of objects). -- @field #string recordId Returns record ID of the object in lowercase. @@ -801,7 +801,7 @@ -- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1); -- * `loop` - a boolean, to set if sound should be repeated when it ends (default: false); -- @usage local params = { --- timeOffset=0.1 +-- timeOffset=0.1, -- volume=0.3, -- loop=false, -- pitch=1.0 @@ -822,7 +822,7 @@ -- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1); -- * `loop` - a boolean, to set if sound should be repeated when it ends (default: false); -- @usage local params = { --- timeOffset=0.1 +-- timeOffset=0.1, -- volume=0.3, -- loop=false, -- pitch=1.0 diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index b2711c895b..6d51987ac7 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1004,7 +1004,7 @@ -- @param openmw.core#GameObject actor NPC object -- @param #string faction Faction ID -- @usage local NPC = require('openmw.types').NPC; --- NPC.clearExpell(player, "mages guild"); +-- NPC.clearExpelled(player, "mages guild"); --- -- Check if NPC is expelled from given faction. From 0b5c8271e0cbd130b802b2a8f4af5ab234b5858d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 18 Jul 2025 21:53:21 +0200 Subject: [PATCH 35/55] ESM4 landscape textures --- apps/openmw/mwrender/terrainstorage.cpp | 13 ++ apps/openmw/mwrender/terrainstorage.hpp | 3 + apps/openmw/mwworld/esmstore.hpp | 3 +- apps/openmw/mwworld/store.cpp | 1 + components/esm/common.hpp | 1 + components/esm/esmterrain.cpp | 6 + components/esm/esmterrain.hpp | 25 +++- components/esm/records.hpp | 1 + components/esm4/loadland.cpp | 154 +++++++++++------------- components/esm4/loadland.hpp | 3 + components/esmterrain/storage.cpp | 148 ++++++++++++++++++++++- components/esmterrain/storage.hpp | 19 ++- components/terrain/chunkmanager.cpp | 5 +- components/terrain/material.cpp | 8 +- components/terrain/material.hpp | 7 +- components/terrain/quadtreeworld.cpp | 5 +- components/terrain/storage.hpp | 3 +- components/terrain/terraingrid.cpp | 6 +- 18 files changed, 304 insertions(+), 107 deletions(-) diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 9776d7e632..0e752701c7 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -1,6 +1,8 @@ #include "terrainstorage.hpp" #include +#include +#include #include #include "../mwbase/environment.hpp" @@ -111,4 +113,15 @@ namespace MWRender return esmStore.get().search(index, plugin); } + const ESM4::LandTexture* TerrainStorage::getEsm4LandTexture(ESM::RefId ltexId) const + { + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + return esmStore.get().search(ltexId); + } + + const ESM4::TextureSet* TerrainStorage::getEsm4TextureSet(ESM::RefId txstId) const + { + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + return esmStore.get().search(txstId); + } } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 731f396713..406894fd9b 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -24,6 +24,9 @@ namespace MWRender osg::ref_ptr getLand(ESM::ExteriorCellLocation cellLocation) override; const std::string* getLandTexture(std::uint16_t index, int plugin) override; + const ESM4::LandTexture* getEsm4LandTexture(ESM::RefId ltexId) const override; + const ESM4::TextureSet* getEsm4TextureSet(ESM::RefId txstId) const override; + bool hasData(ESM::ExteriorCellLocation cellLocation) override; /// Get bounds of the whole terrain in cell units diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 0c37f243e8..fc4ef2713b 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -110,6 +110,7 @@ namespace ESM4 struct Static; struct StaticCollection; struct Terminal; + struct TextureSet; struct Tree; struct Weapon; struct World; @@ -149,7 +150,7 @@ namespace MWWorld Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, - Store, Store, Store, Store>; + Store, Store, Store, Store, Store>; private: template diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 80bcdb056a..e4e67c2f3d 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1354,6 +1354,7 @@ template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; diff --git a/components/esm/common.hpp b/components/esm/common.hpp index 90c60ad6ab..7d149a865c 100644 --- a/components/esm/common.hpp +++ b/components/esm/common.hpp @@ -31,6 +31,7 @@ namespace ESM VER_134 = 0x3fab851f, // FONV, GunRunnersArsenal, LonesomeRoad, OldWorldBlues VER_094 = 0x3f70a3d7, // TES5/FO3 VER_170 = 0x3fd9999a, // TES5 + VER_171 = 0x3fdae148, // TES5 VER_095 = 0x3f733333, // FO4 }; diff --git a/components/esm/esmterrain.cpp b/components/esm/esmterrain.cpp index 4bc6768e51..ec824d5112 100644 --- a/components/esm/esmterrain.cpp +++ b/components/esm/esmterrain.cpp @@ -33,6 +33,7 @@ ESM::LandData::LandData(const ESM::Land& land, int loadFlags) , mNormals(mData->mNormals) , mColors(mData->mColours) , mTextures(mData->mTextures) + , mIsEsm4(false) { } @@ -43,9 +44,11 @@ ESM::LandData::LandData(const ESM4::Land& land, int /*loadFlags*/) , mMaxHeight(std::numeric_limits::lowest()) , mSize(Constants::ESM4CellSizeInUnits) , mLandSize(ESM4::Land::sVertsPerSide) + , mPlugin(land.mId.mContentFile) , mNormals(land.mVertNorm) , mColors(land.mVertColr) , mTextures(textures) + , mIsEsm4(true) { float rowOffset = land.mHeightMap.heightOffset; for (int y = 0; y < mLandSize; y++) @@ -69,6 +72,9 @@ ESM::LandData::LandData(const ESM4::Land& land, int /*loadFlags*/) } mHeights = mHeightsData; + + for (int i = 0; i < 4; ++i) + mEsm4Textures[i] = land.mTextures[i]; } namespace ESM diff --git a/components/esm/esmterrain.hpp b/components/esm/esmterrain.hpp index 991df54c8f..b6b62773d5 100644 --- a/components/esm/esmterrain.hpp +++ b/components/esm/esmterrain.hpp @@ -1,15 +1,13 @@ #ifndef COMPONENTS_ESM_ESMTERRAIN #define COMPONENTS_ESM_ESMTERRAIN +#include #include #include #include #include -namespace ESM4 -{ - struct Land; -} +#include namespace ESM { @@ -28,7 +26,6 @@ namespace ESM std::span getHeights() const { return mHeights; } std::span getNormals() const { return mNormals; } std::span getColors() const { return mColors; } - std::span getTextures() const { return mTextures; } float getSize() const { return mSize; } float getMinHeight() const { return mMinHeight; } float getMaxHeight() const { return mMaxHeight; } @@ -36,6 +33,22 @@ namespace ESM int getLoadFlags() const { return mLoadFlags; } int getPlugin() const { return mPlugin; } + bool isEsm4() const { return mIsEsm4; } + + std::span getTextures() const + { + if (mIsEsm4) + throw std::logic_error("ESM3 textures requested from ESM4 LandData"); + return mTextures; + } + + const ESM4::Land::Texture& getEsm4Texture(std::size_t quad) const + { + if (!mIsEsm4) + throw std::logic_error("ESM4 texture requested from ESM3 LandData"); + return mEsm4Textures[quad]; + } + private: std::unique_ptr mData; int mLoadFlags = 0; @@ -49,6 +62,8 @@ namespace ESM std::span mNormals; std::span mColors; std::span mTextures; + std::array mEsm4Textures; + bool mIsEsm4; }; } diff --git a/components/esm/records.hpp b/components/esm/records.hpp index 0b76fab0ff..3b9f8ccd15 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -79,6 +79,7 @@ #include #include #include +#include #include #include diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp index 53fb1de083..a92b2b5960 100644 --- a/components/esm4/loadland.cpp +++ b/components/esm4/loadland.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + Copyright (C) 2015 - 2024 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -17,7 +17,7 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. - cc9cii cc9c@iinet.net.au + cc9cii cc9cii@hotmail.com Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by @@ -26,13 +26,51 @@ */ #include "loadland.hpp" +#include #include #include #include #include "reader.hpp" -// #include "writer.hpp" + +namespace +{ + void assignDefaultTextures(ESM4::Land& land, ESM4::Reader& reader) + { + std::uint32_t esmVer = reader.esmVersion(); + + // Note: in games after TES4 it can be configured in ini file (sDefaultLandDiffuseTexture) + if (!reader.hasFormVersion() && (esmVer == ESM::VER_080 || esmVer == ESM::VER_100)) // TES4 + { + land.mDefaultDiffuseMap = VFS::Path::NormalizedView("textures/landscape/terrainhddirt01.dds"); + land.mDefaultNormalMap = VFS::Path::NormalizedView("textures/landscape/terrainhddirt01_n.dds"); + } + else if (reader.hasFormVersion() && reader.formVersion() >= 16 + && (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || esmVer == ESM::VER_171)) // TES5 + { + land.mDefaultDiffuseMap = VFS::Path::NormalizedView("textures/landscape/dirt02.dds"); + land.mDefaultNormalMap = VFS::Path::NormalizedView("textures/landscape/dirt02_n.dds"); + } + else if (esmVer == ESM::VER_095 || esmVer == ESM::VER_100) // FO4 + { + land.mDefaultDiffuseMap + = VFS::Path::NormalizedView("textures/landscape/ground/commonwealthdefault01_d.dds"); + land.mDefaultNormalMap = VFS::Path::NormalizedView("textures/landscape/ground/commonwealthdefault01_n.dds"); + } + else if (esmVer == ESM::VER_094 || esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134) + { // FO3, FONV + land.mDefaultDiffuseMap = VFS::Path::NormalizedView("textures/landscape/dirtwasteland01.dds"); + land.mDefaultNormalMap = VFS::Path::NormalizedView("textures/landscape/dirtwasteland01_n.dds"); + } + else + { + // Nothing especially bad happens if default texture is not set (except of the missing texture of course), + // but we throw an error because this case is unexpected and detection logic needs to be updated. + throw std::runtime_error("ESM4::Land unknown ESM version"); + } + } +} // overlap north // @@ -53,12 +91,16 @@ void ESM4::Land::load(ESM4::Reader& reader) { mId = reader.getFormIdFromHeader(); mFlags = reader.hdr().record.flags; + mDataTypes = 0; mCell = reader.currCell(); TxtLayer layer; std::int8_t currentAddQuad = -1; // for VTXT following ATXT + assignDefaultTextures(*this, reader); - // std::map uniqueTextures; // FIXME: for temp testing only + layer.texture.formId = 0; + for (int i = 0; i < 4; ++i) + mTextures[i].base.formId = 0; while (reader.getSubRecordHeader()) { @@ -78,12 +120,6 @@ void ESM4::Land::load(ESM4::Reader& reader) } case ESM::fourCC("VHGT"): // vertex height gradient, 4+33x33+3 = 4+1089+3 = 1096 { -#if 0 - reader.get(mHeightMap.heightOffset); - reader.get(mHeightMap.gradientData); - reader.get(mHeightMap.unknown1); - reader.get(mHeightMap.unknown2); -#endif reader.get(mHeightMap); mDataTypes |= LAND_VHGT; break; @@ -102,13 +138,9 @@ void ESM4::Land::load(ESM4::Reader& reader) if (base.quadrant >= 4) throw std::runtime_error("base texture quadrant index error"); - reader.adjustFormId(base.formId); - mTextures[base.quadrant].base = std::move(base); -#if 0 - std::cout << "Base Texture formid: 0x" - << std::hex << mTextures[base.quadrant].base.formId - << ", quad " << std::dec << (int)base.quadrant << std::endl; -#endif + if (base.formId != 0) + reader.adjustFormId(base.formId); + mTextures[base.quadrant].base = base; } break; } @@ -116,31 +148,23 @@ void ESM4::Land::load(ESM4::Reader& reader) { if (currentAddQuad != -1) { - // FIXME: sometimes there are no VTXT following an ATXT? Just add a dummy one for now - Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex; + // NOTE: sometimes there are no VTXT following an ATXT + layer.data.resize(1); // just one spot + layer.data.back().position = 0; // this corner + layer.data.back().opacity = 0.f; // transparent + + if (layer.texture.layerIndex != mTextures[currentAddQuad].layers.size()) + throw std::runtime_error("ESM4::LAND additional texture skipping layer"); + mTextures[currentAddQuad].layers.push_back(layer); } + reader.get(layer.texture); - reader.adjustFormId(layer.texture.formId); + if (layer.texture.formId != 0) + reader.adjustFormId(layer.texture.formId); if (layer.texture.quadrant >= 4) - throw std::runtime_error("additional texture quadrant index error"); -#if 0 - FormId txt = layer.texture.formId; - std::map::iterator lb = uniqueTextures.lower_bound(txt); - if (lb != uniqueTextures.end() && !(uniqueTextures.key_comp()(txt, lb->first))) - { - lb->second += 1; - } - else - uniqueTextures.insert(lb, std::make_pair(txt, 1)); -#endif -#if 0 - std::cout << "Additional Texture formId: 0x" - << std::hex << layer.texture.formId - << ", quad " << std::dec << (int)layer.texture.quadrant << std::endl; - std::cout << "Additional Texture layer: " - << std::dec << (int)layer.texture.layerIndex << std::endl; -#endif + throw std::runtime_error("ESM4::LAND additional texture quadrant index error"); + currentAddQuad = layer.texture.quadrant; break; } @@ -156,25 +180,17 @@ void ESM4::Land::load(ESM4::Reader& reader) if (count) { layer.data.resize(count); - std::vector::iterator it = layer.data.begin(); - for (; it != layer.data.end(); ++it) - { - reader.get(*it); - // FIXME: debug only - // std::cout << "pos: " << std::dec << (int)(*it).position << std::endl; - } + for (ESM4::Land::VTXT& vtxt : layer.data) + reader.get(vtxt); } - mTextures[currentAddQuad].layers.push_back(layer); - // Assumed that the layers are added in the correct sequence - // FIXME: Knights.esp doesn't seem to observe this - investigate more - // assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size()-1 - //&& "additional texture layer index error"); + if (layer.texture.layerIndex != mTextures[currentAddQuad].layers.size()) + throw std::runtime_error("ESM4::LAND additional texture skipping layer"); + + mTextures[currentAddQuad].layers.push_back(layer); currentAddQuad = -1; layer.data.clear(); - // FIXME: debug only - // std::cout << "VTXT: count " << std::dec << count << std::endl; break; } case ESM::fourCC("VTEX"): // only in Oblivion? @@ -195,44 +211,14 @@ void ESM4::Land::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; default: - throw std::runtime_error("ESM4::LAND::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + throw std::runtime_error("ESM4::LAND - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } if (currentAddQuad != -1) { - // FIXME: not sure if it happens here as well + // not sure if it happens here as well, if so just ignore Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex << " quad " << static_cast(layer.texture.quadrant); - mTextures[currentAddQuad].layers.push_back(layer); } - - bool missing = false; - for (int i = 0; i < 4; ++i) - { - if (mTextures[i].base.formId == 0) - { - // std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " missing base, quad " << i << std::endl; - // std::cout << "layers " << mTextures[i].layers.size() << std::endl; - // NOTE: can't set the default here since FO3/FONV may have different defaults - // mTextures[i].base.formId = 0x000008C0; // TerrainHDDirt01.dds - missing = true; - } - // else - //{ - // std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " base, quad " << i << std::endl; - // std::cout << "layers " << mTextures[i].layers.size() << std::endl; - // } - } - // at least one of the quadrants do not have a base texture, return without setting the flag - if (!missing) - mDataTypes |= LAND_VTEX; } - -// void ESM4::Land::save(ESM4::Writer& writer) const -//{ -// } - -// void ESM4::Land::blank() -//{ -// } diff --git a/components/esm4/loadland.hpp b/components/esm4/loadland.hpp index ccfa009d80..600a235f1c 100644 --- a/components/esm4/loadland.hpp +++ b/components/esm4/loadland.hpp @@ -32,6 +32,7 @@ #include #include +#include namespace ESM4 { @@ -124,6 +125,8 @@ namespace ESM4 Texture mTextures[4]; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right std::vector mIds; // land texture (LTEX) formids ESM::FormId mCell; + VFS::Path::NormalizedView mDefaultDiffuseMap; + VFS::Path::NormalizedView mDefaultNormalMap; void load(Reader& reader); diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 35ec814aa2..1a07f6fe0a 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -89,6 +91,8 @@ namespace ESMTerrain LandObject::LandObject(const ESM4::Land& land, int loadFlags) : mData(land, loadFlags) { + mEsm4DefaultLayerInfo.mDiffuseMap = land.mDefaultDiffuseMap; + mEsm4DefaultLayerInfo.mNormalMap = land.mDefaultNormalMap; } LandObject::LandObject(const ESM::Land& land, int loadFlags) @@ -385,9 +389,105 @@ namespace ESMTerrain return Misc::ResourceHelpers::correctTexturePath(texture, mVFS); } + void Storage::getEsm4Blendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, + std::vector& layerList, ESM::RefId worldspace) + { + const osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize - 1, chunkSize + 1) * 0.5f; + const int startCellX = static_cast(std::floor(origin.x())); + const int startCellY = static_cast(std::floor(origin.y())); + + constexpr int quadsPerCell = 2; + constexpr int quadSize = ESM4::Land::sVertsPerSide / quadsPerCell; + const int quadCount = static_cast(chunkSize * quadsPerCell); + assert(quadCount > 0); + + const int blendmapSize = quadCount * quadSize + 1; + + LandCache cache(startCellX - 1, startCellY - 1, static_cast(std::ceil(chunkSize)) + 2); + std::pair lastCell{ startCellX, startCellY }; + const LandObject* land = getLand(ESM::ExteriorCellLocation(startCellX, startCellY, worldspace), cache); + + std::map textureIndicesMap; + + auto getOrCreateBlendmap = [&](ESM::FormId texId) -> unsigned char* { + auto found = textureIndicesMap.find(texId); + if (found != textureIndicesMap.end()) + return blendmaps[found->second]->data(); + Terrain::LayerInfo info + = texId.isZeroOrUnset() ? land->getEsm4DefaultLayerInfo() : getLandTextureLayerInfo(texId); + osg::ref_ptr image(new osg::Image); + image->allocateImage(blendmapSize, blendmapSize, 1, GL_ALPHA, GL_UNSIGNED_BYTE); + std::memset(image->data(), 0, image->getTotalDataSize()); + textureIndicesMap.emplace(texId, blendmaps.size()); + blendmaps.push_back(std::move(image)); + layerList.push_back(std::move(info)); + return blendmaps.back()->data(); + }; + + const auto handleSample = [&](const CellSample& sample) { + const std::pair cell{ sample.mCellX, sample.mCellY }; + if (lastCell != cell) + { + land = getLand(ESM::ExteriorCellLocation(sample.mCellX, sample.mCellY, worldspace), cache); + lastCell = cell; + } + if (!land) + return; + const ESM::LandData* ldata = land->getData(0); + if (!ldata) + return; + int quad; + if (sample.mSrcRow == 0) + quad = sample.mSrcCol == 0 ? 0 : 2; + else + quad = sample.mSrcCol == 0 ? 1 : 3; + const ESM4::Land::Texture& ltex = ldata->getEsm4Texture(quad); + + unsigned char* const baseBlendmap = getOrCreateBlendmap(ESM::FormId::fromUint32(ltex.base.formId)); + int starty = (static_cast(sample.mDstCol) - 1) * quadSize; + int startx = sample.mDstRow * quadSize; + for (int y = std::max(0, starty + 1); y <= starty + quadSize && y < blendmapSize; ++y) + { + unsigned char* const row = baseBlendmap + (blendmapSize - y - 1) * blendmapSize; + for (int x = startx; x < startx + quadSize && x < blendmapSize; ++x) + row[x] = 255; + } + + for (const auto& layer : ltex.layers) + { + unsigned char* const layerBlendmap = getOrCreateBlendmap(ESM::FormId::fromUint32(layer.texture.formId)); + for (const ESM4::Land::VTXT& v : layer.data) + { + int y = v.position / (quadSize + 1); + int x = v.position % (quadSize + 1); + if (x == quadSize || startx + x >= blendmapSize || y == 0 || starty + y >= blendmapSize + || starty + y < 0) + { + continue; + } + int index = (blendmapSize - starty - y - 1) * blendmapSize + startx + x; + int delta = std::clamp(static_cast(v.opacity * 255), 0, 255); + baseBlendmap[index] = std::max(0, baseBlendmap[index] - delta); + layerBlendmap[index] = delta; + } + } + }; + + sampleBlendmaps(chunkSize, origin.x(), origin.y(), quadsPerCell, handleSample); + + if (blendmaps.size() == 1) + blendmaps.clear(); // If a single texture fills the whole terrain, there is no need to blend + } + void Storage::getBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, std::vector& layerList, ESM::RefId worldspace) { + if (ESM::isEsm4Ext(worldspace)) + { + getEsm4Blendmaps(chunkSize, chunkCenter, blendmaps, layerList, worldspace); + return; + } + const osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize, chunkSize) * 0.5f; const int startCellX = static_cast(std::floor(origin.x())); const int startCellY = static_cast(std::floor(origin.y())); @@ -613,6 +713,47 @@ namespace ESMTerrain return info; } + Terrain::LayerInfo Storage::getLandTextureLayerInfo(ESM::FormId id) + { + if (const ESM4::LandTexture* ltex = getEsm4LandTexture(id)) + { + if (!ltex->mTextureFile.empty()) + return getLayerInfo("textures/landscape/" + ltex->mTextureFile); // TES4 + if (const ESM4::TextureSet* txst = getEsm4TextureSet(ltex->mTexture)) + return getTextureSetLayerInfo(*txst); // TES5 + else + Log(Debug::Warning) << "TextureSet not found: " << ltex->mTexture.toString(); + } + else + Log(Debug::Warning) << "LandTexture not found: " << id.toString(); + return getLayerInfo(""); + } + + Terrain::LayerInfo Storage::getTextureSetLayerInfo(const ESM4::TextureSet& txst) + { + Terrain::LayerInfo info; + + assert(!txst.mDiffuse.empty() && "getlayerInfo: empty diffuse map"); + info.mDiffuseMap = "textures/" + txst.mDiffuse; + + if (!txst.mNormalMap.empty()) + info.mNormalMap = "textures/" + txst.mNormalMap; + + // FIXME: this flag indicates height info in alpha channel of normal map + // but the normal map alpha channel has specular info instead + // (probably needs some flag in the terrain shader to fix) + info.mParallax = false; + // FIXME: this flag indicates specular info in alpha channel of diffuse + // but the diffuse alpha channel has transparency data instead + // (probably needs some flag in the terrain shader to fix) + info.mSpecular = false; + + // FIXME: should support other features of ESM4::TextureSet + // probably need corresponding support in the terrain shader + + return info; + } + float Storage::getCellWorldSize(ESM::RefId worldspace) { return static_cast(ESM::getCellSize(worldspace)); @@ -623,9 +764,12 @@ namespace ESMTerrain return ESM::getLandSize(worldspace); } - int Storage::getBlendmapScale(float chunkSize) + int Storage::getTextureTileCount(float chunkSize, ESM::RefId worldspace) { - return ESM::Land::LAND_TEXTURE_SIZE * chunkSize; + if (ESM::isEsm4Ext(worldspace)) + return static_cast(2 * ESM4::Land::sQuadTexturePerSide * chunkSize); + else + return static_cast(ESM::Land::LAND_TEXTURE_SIZE * chunkSize); } } diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 402f2147ab..be3ba87751 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -13,6 +14,8 @@ namespace ESM4 { struct Land; + struct LandTexture; + struct TextureSet; } namespace ESM @@ -51,9 +54,13 @@ namespace ESMTerrain int getPlugin() const { return mData.getPlugin(); } + const Terrain::LayerInfo& getEsm4DefaultLayerInfo() const { return mEsm4DefaultLayerInfo; } + private: ESM::LandData mData; + Terrain::LayerInfo mEsm4DefaultLayerInfo; + LandObject(const LandObject& copy, const osg::CopyOp& copyOp); }; @@ -74,6 +81,11 @@ namespace ESMTerrain // Not implemented in this class, because we need different Store implementations for game and editor virtual osg::ref_ptr getLand(ESM::ExteriorCellLocation cellLocation) = 0; virtual const std::string* getLandTexture(std::uint16_t index, int plugin) = 0; + + // Not implemented in this class because requires ESMStore + virtual const ESM4::LandTexture* getEsm4LandTexture(ESM::RefId ltexId) const { return nullptr; } + virtual const ESM4::TextureSet* getEsm4TextureSet(ESM::RefId txstId) const { return nullptr; } + /// Get bounds of the whole terrain in cell units void getBounds(float& minX, float& maxX, float& minY, float& maxY, ESM::RefId worldspace) override = 0; @@ -120,7 +132,7 @@ namespace ESMTerrain /// Get the number of vertices on one side for each cell. Should be (power of two)+1 int getCellVertices(ESM::RefId worldspace) override; - int getBlendmapScale(float chunkSize) override; + int getTextureTileCount(float chunkSize, ESM::RefId worldspace) override; float getVertexHeight(const ESM::LandData* data, int x, int y) { @@ -159,6 +171,11 @@ namespace ESMTerrain bool mAutoUseSpecularMaps; Terrain::LayerInfo getLayerInfo(const std::string& texture); + Terrain::LayerInfo getTextureSetLayerInfo(const ESM4::TextureSet& txst); + Terrain::LayerInfo getLandTextureLayerInfo(ESM::FormId id); + + void getEsm4Blendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, + std::vector& layerList, ESM::RefId worldspace); }; } diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 3d7c2b1dfc..242f3e5700 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -5,6 +5,7 @@ #include +#include #include #include @@ -205,10 +206,10 @@ namespace Terrain blendmapTextures.push_back(texture); } - float blendmapScale = mStorage->getBlendmapScale(chunkSize); + float tileCount = mStorage->getTextureTileCount(chunkSize, mWorldspace); return ::Terrain::createPasses( - useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale); + useShaders, mSceneManager, layers, blendmapTextures, tileCount, tileCount, ESM::isEsm4Ext(mWorldspace)); } osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f& chunkCenter, unsigned char lod, diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 9c3a7f589d..350b174d69 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -224,7 +224,7 @@ namespace Terrain { std::vector> createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector& layers, const std::vector>& blendmaps, - int blendmapScale, float layerTileSize) + int blendmapScale, float layerTileSize, bool esm4terrain) { auto& shaderManager = sceneManager->getShaderManager(); std::vector> passes; @@ -269,7 +269,8 @@ namespace Terrain osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); stateset->setTextureAttributeAndModes(1, blendmap.get()); - stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); + if (!esm4terrain) + stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); stateset->addUniform(UniformCollection::value().mBlendMap); } @@ -329,7 +330,8 @@ namespace Terrain stateset->setTextureAttributeAndModes(1, blendmap.get()); // This is to map corner vertices directly to the center of a blendmap texel. - stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); + if (!esm4terrain) + stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); stateset->setTextureAttributeAndModes(1, TexEnvCombine::value(), osg::StateAttribute::ON); } } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 1dbf6d8fc8..fca14d8b3e 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -20,14 +20,13 @@ namespace Terrain { osg::ref_ptr mDiffuseMap; osg::ref_ptr mNormalMap; // optional - bool mParallax; - bool mSpecular; + bool mParallax = false; + bool mSpecular = false; }; std::vector> createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector& layers, const std::vector>& blendmaps, - int blendmapScale, float layerTileSize); - + int blendmapScale, float layerTileSize, bool esm4terrain = false); } #endif diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 99938d7b3a..0d7f1b7762 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -289,11 +289,12 @@ namespace Terrain , mLodFactor(lodFactor) , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits::max()) - , mMinSize(ESM::isEsm4Ext(worldspace) ? 1 / 4.f : 1 / 8.f) + , mMinSize(ESM::isEsm4Ext(worldspace) ? 1 / 2.f : 1 / 8.f) , mDebugTerrainChunks(debugChunks) { mChunkManager->setCompositeMapSize(compMapResolution); - mChunkManager->setCompositeMapLevel(compMapLevel); + mChunkManager->setCompositeMapLevel( + ESM::isEsm4Ext(worldspace) ? compMapLevel * 2 /*because cells are twice smaller*/ : compMapLevel); mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize); mChunkManagers.push_back(mChunkManager.get()); diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index b1007c13e6..38aee20b8b 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -88,7 +88,8 @@ namespace Terrain /// Get the number of vertices on one side for each cell. Should be (power of two)+1 virtual int getCellVertices(ESM::RefId worldspace) = 0; - virtual int getBlendmapScale(float chunkSize) = 0; + /// Get the number of texture tiles on one side per chunk (chunkSize 1.0 = 1 cell). + virtual int getTextureTileCount(float chunkSize, ESM::RefId worldspace) = 0; }; } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 88fcaf667d..aff72d2d79 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -9,6 +9,8 @@ #include "heightcull.hpp" #include "storage.hpp" #include "view.hpp" + +#include #include namespace Terrain @@ -27,13 +29,13 @@ namespace Terrain unsigned int borderMask) : Terrain::World( parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask, worldspace, expiryDelay) - , mNumSplits(4) + , mNumSplits(ESM::isEsm4Ext(worldspace) ? 2 : 4) { } TerrainGrid::TerrainGrid(osg::Group* parent, Storage* storage, ESM::RefId worldspace, unsigned int nodeMask) : Terrain::World(parent, storage, nodeMask, worldspace) - , mNumSplits(4) + , mNumSplits(ESM::isEsm4Ext(worldspace) ? 2 : 4) { } From 05d67ef4125fdac6e9c229e8de01faebb374520a Mon Sep 17 00:00:00 2001 From: epochwon Date: Fri, 1 Aug 2025 14:47:54 -0400 Subject: [PATCH 36/55] swedish lines --- files/lang/launcher_sv.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts index db3b64521f..c20b733c4e 100644 --- a/files/lang/launcher_sv.ts +++ b/files/lang/launcher_sv.ts @@ -1468,11 +1468,11 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> - + <html><head/><body><p>Kontrollerar styrkan på dopplereffekten. Noll innebär helt inaktiverat.</p><p>Dopplereffekten höjer eller sänker tonhöjden på ljud i förhållande till ljudkällans hastighet och lyssnaren.</p></body></html> Doppler Factor - + Dopplerfaktor From 5e64015aa9964f2f21f0dfb65e9ec9243cf304c0 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 1 Aug 2025 15:57:31 -0700 Subject: [PATCH 37/55] Fix typo, remove change --- files/lua_api/openmw/animation.lua | 2 +- files/lua_api/openmw/core.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/files/lua_api/openmw/animation.lua b/files/lua_api/openmw/animation.lua index b182a1e0af..8237d3ca94 100644 --- a/files/lua_api/openmw/animation.lua +++ b/files/lua_api/openmw/animation.lua @@ -254,7 +254,7 @@ -- Can only be used on self. -- @function [parent=#animation] removeVfx -- @param openmw.core#GameObject actor --- @param #string vfxId an integer ID that uniquely identifies the VFX to remove +-- @param #string vfxId an string ID that uniquely identifies the VFX to remove --- -- Removes all vfx from the actor. diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 43bb22058b..c1b888bd43 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -167,7 +167,7 @@ -- @field openmw.util#Transform startingRotation The object original rotation -- @field #ObjectOwner owner Ownership information -- @field #Cell cell The cell where the object currently is. During loading a game and for objects in an inventory or a container `cell` is nil. --- @field #GameObject parentContainer Container or actor that contains this object (or has in inventory) . It is nil if the object is in a cell. +-- @field #GameObject parentContainer Container or actor that contains (or has in inventory) this object. It is nil if the object is in a cell. -- @field #any type Type of the object (one of the tables from the package @{openmw.types#types}). -- @field #number count Count (>1 means a stack of objects). -- @field #string recordId Returns record ID of the object in lowercase. From 6464e2a11b51241f6b1f555923a0dc6a0bd56ad7 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 1 Aug 2025 16:11:32 -0700 Subject: [PATCH 38/55] remove N --- files/lua_api/openmw/animation.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lua_api/openmw/animation.lua b/files/lua_api/openmw/animation.lua index 8237d3ca94..9fcbe8ea21 100644 --- a/files/lua_api/openmw/animation.lua +++ b/files/lua_api/openmw/animation.lua @@ -254,7 +254,7 @@ -- Can only be used on self. -- @function [parent=#animation] removeVfx -- @param openmw.core#GameObject actor --- @param #string vfxId an string ID that uniquely identifies the VFX to remove +-- @param #string vfxId a string ID that uniquely identifies the VFX to remove --- -- Removes all vfx from the actor. From 4c4d6078d89a8bb8f003479b6ba4d0db474debf8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 6 Jul 2025 21:57:47 +0300 Subject: [PATCH 39/55] Training menu fixes (#8584) Rework the layout Use "OK" instead of "Cancel" for the OK button Add gp to the price Use entry spacing consistent with other service menus --- apps/openmw/mwgui/trainingwindow.cpp | 6 +++--- files/data/mygui/openmw_trainingwindow.layout | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 4bde77a552..3fc8412d4c 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -29,7 +29,7 @@ namespace MWGui : WindowBase("openmw_trainingwindow.layout") { getWidget(mTrainingOptions, "TrainingOptions"); - getWidget(mCancelButton, "CancelButton"); + getWidget(mCancelButton, "OkButton"); getWidget(mPlayerGold, "PlayerGold"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onCancelButtonClicked); @@ -115,14 +115,14 @@ namespace MWGui MyGUI::Button* button = mTrainingOptions->createWidget(price <= playerGold ? "SandTextButton" : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip - MyGUI::IntCoord(5, 5 + i * lineHeight, mTrainingOptions->getWidth() - 10, lineHeight), + MyGUI::IntCoord(4, 3 + i * lineHeight, mTrainingOptions->getWidth() - 10, lineHeight), MyGUI::Align::Default); button->setUserData(skills[i].first); button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected); button->setCaptionWithReplacing( - MyGUI::TextIterator::toTagsString(skill->mName) + " - " + MyGUI::utility::toString(price)); + MyGUI::TextIterator::toTagsString(skill->mName) + " - " + MyGUI::utility::toString(price) + "#{sgp}"); button->setSize(button->getTextSize().width + 12, button->getSize().height); diff --git a/files/data/mygui/openmw_trainingwindow.layout b/files/data/mygui/openmw_trainingwindow.layout index 80526068ce..f3cadcaf46 100644 --- a/files/data/mygui/openmw_trainingwindow.layout +++ b/files/data/mygui/openmw_trainingwindow.layout @@ -1,28 +1,28 @@ - + - + - + - + - + - + - + From 978d8668f4f0ed3711858dc8c613de3c58301730 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 2 Aug 2025 08:39:13 +0000 Subject: [PATCH 40/55] Bring jail time skill changes over to lua --- CMakeLists.txt | 2 +- apps/openmw/mwbase/luamanager.hpp | 1 + apps/openmw/mwgui/jailscreen.cpp | 43 +----------- apps/openmw/mwlua/engineevents.cpp | 9 +++ apps/openmw/mwlua/engineevents.hpp | 7 +- apps/openmw/mwlua/localscripts.cpp | 2 +- apps/openmw/mwlua/localscripts.hpp | 2 + apps/openmw/mwlua/luamanagerimp.cpp | 5 ++ apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwlua/uibindings.cpp | 4 ++ .../omw/mechanics/playercontroller.lua | 68 +++++++++++++++---- files/data/scripts/omw/skillhandlers.lua | 35 ++++++---- files/data/scripts/omw/ui.lua | 9 ++- 13 files changed, 119 insertions(+), 69 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b4ef872eb6..57ebeefcfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 50) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 86) +set(OPENMW_LUA_API_REVISION 87) set(OPENMW_POSTPROCESSING_API_REVISION 3) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 61574de3ac..5772c555a3 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -75,6 +75,7 @@ namespace MWBase const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) = 0; + virtual void jailTimeServed(const MWWorld::Ptr& actor, int days) = 0; virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0; virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; virtual void onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon, diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 2fbaa8d8ac..9244e9dd2f 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -4,6 +4,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -86,46 +87,6 @@ namespace MWGui // We should not worsen corprus when in prison player.getClass().getCreatureStats(player).getActiveSpells().skipWorsenings(mDays * 24); - - const auto& skillStore = MWBase::Environment::get().getESMStore()->get(); - std::set skills; - for (int day = 0; day < mDays; ++day) - { - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - const ESM::Skill* skill = skillStore.searchRandom({}, prng); - skills.insert(skill); - - MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill->mId); - if (skill->mId == ESM::Skill::Security || skill->mId == ESM::Skill::Sneak) - value.setBase(std::min(100.f, value.getBase() + 1)); - else - value.setBase(std::max(0.f, value.getBase() - 1)); - } - - const MWWorld::Store& gmst - = MWBase::Environment::get().getESMStore()->get(); - - std::string message; - if (mDays == 1) - message = gmst.find("sNotifyMessage42")->mValue.getString(); - else - message = gmst.find("sNotifyMessage43")->mValue.getString(); - - message = Misc::StringUtils::format(message, mDays); - - for (const ESM::Skill* skill : skills) - { - int skillValue = player.getClass().getNpcStats(player).getSkill(skill->mId).getBase(); - std::string skillMsg = gmst.find("sNotifyMessage44")->mValue.getString(); - if (skill->mId == ESM::Skill::Sneak || skill->mId == ESM::Skill::Security) - skillMsg = gmst.find("sNotifyMessage39")->mValue.getString(); - - skillMsg = Misc::StringUtils::format(skillMsg, skill->mName, skillValue); - message += "\n" + skillMsg; - } - - std::vector buttons; - buttons.emplace_back("#{Interface:OK}"); - MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons); + MWBase::Environment::get().getLuaManager()->jailTimeServed(player, mDays); } } diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 6c652bccba..be7d249bfc 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -113,6 +113,15 @@ namespace MWLua scripts->onSkillLevelUp(event.mSkill, event.mSource); } + void operator()(const OnJailTimeServed& event) const + { + MWWorld::Ptr actor = getPtr(event.mActor); + if (actor.isEmpty()) + return; + if (auto* scripts = getLocalScripts(actor)) + scripts->onJailTimeServed(event.mDays); + } + private: MWWorld::Ptr getPtr(ESM::RefNum id) const { diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index fb9183eb7c..407739d60e 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -70,8 +70,13 @@ namespace MWLua std::string mSkill; std::string mSource; }; + struct OnJailTimeServed + { + ESM::RefNum mActor; + int mDays; + }; using Event = std::variant; + OnAnimationTextKey, OnSkillUse, OnSkillLevelUp, OnJailTimeServed>; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index d784328035..ad2913dd49 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -232,7 +232,7 @@ namespace MWLua [&](LuaUtil::LuaView& view) { addPackage("openmw.self", sol::make_object(view.sol(), &mData)); }); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse, - &mOnSkillLevelUp }); + &mOnSkillLevelUp, &mOnJailTimeServed }); } void LocalScripts::setActive(bool active, bool callHandlers) diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index b3ec647d0e..bc34576509 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -89,6 +89,7 @@ namespace MWLua { callEngineHandlers(mOnSkillLevelUp, skillId, source); } + void onJailTimeServed(int days) { callEngineHandlers(mOnJailTimeServed, days); } void applyStatsCache(); @@ -118,6 +119,7 @@ namespace MWLua EngineHandlerList mOnPlayAnimationHandlers{ "_onPlayAnimation" }; EngineHandlerList mOnSkillUse{ "_onSkillUse" }; EngineHandlerList mOnSkillLevelUp{ "_onSkillLevelUp" }; + EngineHandlerList mOnJailTimeServed{ "_onJailTimeServed" }; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index df9c9cd50f..850dd87eed 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -490,6 +490,11 @@ namespace MWLua EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) }); } + void LuaManager::jailTimeServed(const MWWorld::Ptr& actor, int days) + { + mEngineEvents.addToQueue(EngineEvents::OnJailTimeServed{ getId(actor), days }); + } + void LuaManager::onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, int attackType, float attackStrength, float damage, bool isHealth, const osg::Vec3f& hitPos, bool successful, MWMechanics::DamageSourceType sourceType) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index f47b6f96cf..bd4ab0d30e 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -92,6 +92,7 @@ namespace MWLua bool loopfallback) override; void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) override; void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) override; + void jailTimeServed(const MWWorld::Ptr& actor, int days) override; void onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, int attackType, float attackStrength, float damage, bool isHealth, const osg::Vec3f& hitPos, bool successful, MWMechanics::DamageSourceType sourceType) override; diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 826338ca7d..8d9892005c 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -108,6 +108,10 @@ namespace MWLua } luaManager->addUIMessage(message, mode); }; + + api["_showInteractiveMessage"] = [windowManager](std::string_view message, sol::optional) { + windowManager->interactiveMessageBox(message, { "#{Interface:OK}" }); + }; api["CONSOLE_COLOR"] = LuaUtil::makeStrictReadOnly(LuaUtil::tableFromPairs(lua, { { "Default", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Default.substr(1)) }, diff --git a/files/data/scripts/omw/mechanics/playercontroller.lua b/files/data/scripts/omw/mechanics/playercontroller.lua index 6de31afdea..a0d1b11362 100644 --- a/files/data/scripts/omw/mechanics/playercontroller.lua +++ b/files/data/scripts/omw/mechanics/playercontroller.lua @@ -39,7 +39,8 @@ end local function skillLevelUpHandler(skillid, source, params) local skillStat = NPC.stats.skills[skillid](self) - if skillStat.base >= 100 then + if (skillStat.base >= 100 and params.skillIncreaseValue > 0) or + (skillStat.base <= 0 and params.skillIncreaseValue < 0) then return false end @@ -62,25 +63,67 @@ local function skillLevelUpHandler(skillid, source, params) = levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + params.levelUpSpecializationIncreaseValue; end - local skillRecord = Skill.record(skillid) - local npcRecord = NPC.record(self) - local class = NPC.classes.record(npcRecord.class) + if source ~= 'jail' then + local skillRecord = Skill.record(skillid) + local npcRecord = NPC.record(self) + local class = NPC.classes.record(npcRecord.class) - ambient.playSound("skillraise") + ambient.playSound("skillraise") - local message = string.format(core.getGMST('sNotifyMessage39'),skillRecord.name,skillStat.base) + local message = string.format(core.getGMST('sNotifyMessage39'),skillRecord.name,skillStat.base) - if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then - message = '#{sBookSkillMessage}\n'..message + if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then + message = '#{sBookSkillMessage}\n'..message + end + + ui.showMessage(message, { showInDialogue = false }) + + if levelStat.progress >= core.getGMST('iLevelUpTotal') then + ui.showMessage('#{sLevelUpMsg}', { showInDialogue = false }) + end + + if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end end +end - ui.showMessage(message, { showInDialogue = false }) +local function jailTimeServed(days) + if not days or days <= 0 then + return + end - if levelStat.progress >= core.getGMST('iLevelUpTotal') then - ui.showMessage('#{sLevelUpMsg}', { showInDialogue = false }) + local oldSkillLevels = {} + local skillByNumber = {} + for skillid, skillStat in pairs(NPC.stats.skills) do + oldSkillLevels[skillid] = skillStat(self).base + skillByNumber[#skillByNumber+1] = skillid + end + + math.randomseed(core.getSimulationTime()) + for day=1,days do + local skillid = skillByNumber[math.random(#skillByNumber)] + -- skillLevelUp() handles skill-based increase/decrease + I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Jail) end - if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end + local message = '' + if days == 1 then + message = string.format(core.getGMST('sNotifyMessage42'), days) + else + message = string.format(core.getGMST('sNotifyMessage43'), days) + end + for skillid, skillStat in pairs(NPC.stats.skills) do + local diff = skillStat(self).base - oldSkillLevels[skillid] + if diff ~= 0 then + local skillMsg = core.getGMST('sNotifyMessage39') + if diff < 0 then + skillMsg = core.getGMST('sNotifyMessage44') + end + local skillRecord = Skill.record(skillid) + message = message..'\n'..string.format(skillMsg, skillRecord.name, skillStat(self).base) + end + end + + I.UI.showInteractiveMessage(message) end local function skillUsedHandler(skillid, params) @@ -114,6 +157,7 @@ I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler) return { engineHandlers = { onUpdate = onUpdate, + _onJailTimeServed = jailTimeServed, }, eventHandlers = { diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index d3a224dc46..9b58d81174 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -38,6 +38,7 @@ local Skill = core.stats.Skill -- Table of all existing sources for skill increases. Any sources not listed below will be treated as equal to Trainer. -- @type SkillLevelUpSource -- @field #string Book book +-- @field #string Jail jail -- @field #string Trainer trainer -- @field #string Usage usage @@ -131,15 +132,17 @@ local function skillLevelUp(skillid, source) levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMajorMultAttribute') end - local options = - { - skillIncreaseValue = 1, - levelUpProgress = levelUpProgress, - levelUpAttribute = skillRecord.attribute, - levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue, - levelUpSpecialization = skillRecord.specialization, - levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization'), - } + local options = {} + if source == 'jail' and not (skillid == 'security' or skillid == 'sneak') then + options.skillIncreaseValue = -1 + else + options.skillIncreaseValue = 1 + options.levelUpProgress = levelUpProgress + options.levelUpAttribute = skillRecord.attribute + options.levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue + options.levelUpSpecialization = skillRecord.specialization + options.levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization') + end for i = #skillLevelUpHandlers, 1, -1 do if skillLevelUpHandlers[i](skillid, source, options) == false then @@ -156,8 +159,15 @@ return { -- @context local -- @usage local I = require('openmw.interfaces') -- + -- -- Make jail time hurt sneak skill instead of benefitting it + -- I.SkillProgression.addSkillLevelUpHandler(function(skillid, source, options) + -- if skillid == 'sneak' and source == 'jail' and options.skillIncreaseValue > 0 then + -- options.skillIncreaseValue = -options.skillIncreaseValue + -- end + -- end) + -- -- -- Forbid increasing destruction skill past 50 - -- I.SkillProgression.addSkillLevelUpHandler(function(skillid, options) + -- I.SkillProgression.addSkillLevelUpHandler(function(skillid, source, options) -- if skillid == 'destruction' and types.NPC.stats.skills.destruction(self).base >= 50 then -- return false -- end @@ -187,7 +197,7 @@ return { -- a modifiable table of skill level up values, and can be modified to change the behavior of later handlers. -- These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: -- - -- * `skillIncreaseValue` - The numeric amount of skill levels gained. + -- * `skillIncreaseValue` - The numeric amount of skill levels gained. By default this is 1, except when the source is jail in which case it will instead be -1 for all skills except sneak and security. -- * `levelUpProgress` - The numeric amount of level up progress gained. -- * `levelUpAttribute` - The string identifying the attribute that should receive points from this skill level up. -- * `levelUpAttributeIncreaseValue` - The numeric amount of attribute increase points received. This contributes to the amount of each attribute the character receives during a vanilla level up. @@ -263,7 +273,7 @@ return { --- Trigger a skill level up, activating relevant handlers -- @function [parent=#SkillProgression] skillLevelUp -- @param #string skillid The id of the skill to level up. - -- @param #SkillLevelUpSource source The source of the skill increase. + -- @param #SkillLevelUpSource source The source of the skill increase. Note that passing a value of @{#SkillLevelUpSource.Jail} will cause a skill decrease for all skills except sneak and security. skillLevelUp = skillLevelUp, --- @{#SkillLevelUpSource} @@ -272,6 +282,7 @@ return { Book = 'book', Usage = 'usage', Trainer = 'trainer', + Jail = 'jail', }, --- Compute the total skill gain required to level up a skill based on its current level, and other modifying factors such as major skills and specialization. diff --git a/files/data/scripts/omw/ui.lua b/files/data/scripts/omw/ui.lua index 31502fd6ee..c63c085fab 100644 --- a/files/data/scripts/omw/ui.lua +++ b/files/data/scripts/omw/ui.lua @@ -171,7 +171,7 @@ return { interface = { --- Interface version -- @field [parent=#UI] #number version - version = 2, + version = 3, --- All available UI modes. -- Use `view(I.UI.MODE)` in `luap` console mode to see the list. @@ -254,6 +254,13 @@ return { -- @return #boolean isWindowVisible = isWindowVisible, + --- + -- Shows a message as an interactive message box pausing the game, with a single button with the localized text OK. + -- @function [parent=#UI] showInteractiveMessage + -- @param #string message Message to display + -- @param #table options Options (none yet) + showInteractiveMessage = ui._showInteractiveMessage + -- TODO -- registerHudElement = function(name, showFn, hideFn) end, -- showHudElement = function(name, bool) end, From 05f0b4cdbdb4d32b98709b547faff59d498a5f62 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 2 Aug 2025 10:50:43 +0200 Subject: [PATCH 41/55] Use the default clang version in Coverity --- .gitlab-ci.yml | 8 ++++---- CI/install_debian_deps.sh | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fedc707bd7..0d87ed9e03 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -99,8 +99,8 @@ Coverity: - saas-linux-medium-amd64 image: ubuntu:24.04 stage: build - rules: - - if: $CI_PIPELINE_SOURCE == "schedule" +# rules: +# - if: $CI_PIPELINE_SOURCE == "schedule" cache: key: Coverity.ubuntu_24.04.v1 paths: @@ -108,8 +108,8 @@ Coverity: - ccache/ variables: CCACHE_SIZE: 2G - CC: clang-12 - CXX: clang++-12 + CC: clang + CXX: clang++ CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -O0 before_script: diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 4f0e7cdb69..7f50a2eb1a 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -11,7 +11,7 @@ print_help() { declare -rA GROUPED_DEPS=( [gcc]="binutils gcc build-essential cmake ccache curl unzip git pkg-config mold" [clang]="binutils clang make cmake ccache curl unzip git pkg-config mold" - [coverity]="binutils clang-12 make cmake ccache curl unzip git pkg-config" + [coverity]="binutils clang make cmake ccache curl unzip git pkg-config" [gcc_preprocess]=" binutils build-essential From 550b0c985f3f47fb401b173955ddbee92d71c5be Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 2 Aug 2025 10:56:50 +0200 Subject: [PATCH 42/55] Include file --- CI/install_debian_deps.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 7f50a2eb1a..d4a7e88ed6 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -11,7 +11,7 @@ print_help() { declare -rA GROUPED_DEPS=( [gcc]="binutils gcc build-essential cmake ccache curl unzip git pkg-config mold" [clang]="binutils clang make cmake ccache curl unzip git pkg-config mold" - [coverity]="binutils clang make cmake ccache curl unzip git pkg-config" + [coverity]="binutils clang make cmake ccache curl unzip git pkg-config file" [gcc_preprocess]=" binutils build-essential From a2bc1569e0027c177fe347fdbb08f153bf21d1bd Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 20 Apr 2025 17:53:08 +0200 Subject: [PATCH 43/55] Remove unused includes --- apps/openmw/engine.cpp | 4 ++-- apps/openmw/mwlua/animationbindings.cpp | 9 --------- apps/openmw/mwlua/birthsignbindings.cpp | 2 +- apps/openmw/mwlua/cellbindings.cpp | 4 ---- apps/openmw/mwlua/classbindings.cpp | 2 +- apps/openmw/mwlua/corebindings.cpp | 1 - apps/openmw/mwlua/localscripts.cpp | 4 ---- apps/openmw/mwlua/localscripts.hpp | 4 +--- apps/openmw/mwlua/luamanagerimp.cpp | 5 ++++- apps/openmw/mwlua/luamanagerimp.hpp | 3 ++- apps/openmw/mwlua/nearbybindings.cpp | 2 ++ apps/openmw/mwlua/object.hpp | 2 -- apps/openmw/mwlua/objectlists.cpp | 4 ---- apps/openmw/mwlua/racebindings.cpp | 2 +- apps/openmw/mwlua/recordstore.hpp | 12 +++++++++--- apps/openmw/mwlua/stats.cpp | 4 +++- apps/openmw/mwlua/userdataserializer.hpp | 3 ++- apps/openmw/mwlua/worldbindings.cpp | 1 - components/detournavigator/dbrefgeometryobject.hpp | 1 - components/detournavigator/generatenavmeshtile.cpp | 4 +--- components/detournavigator/makenavmesh.cpp | 4 ---- components/detournavigator/navmeshtileview.cpp | 1 - components/detournavigator/offmeshconnection.hpp | 1 - components/detournavigator/preparednavmeshdata.cpp | 2 -- components/detournavigator/raycast.hpp | 3 ++- components/detournavigator/recastmesh.hpp | 2 -- components/files/configurationmanager.cpp | 1 + components/files/configurationmanager.hpp | 1 - components/sceneutil/mwshadowtechnique.cpp | 1 - 29 files changed, 32 insertions(+), 57 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 0ea8451774..2a66878a04 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -5,7 +5,8 @@ #include #include -#include +#include +#include #include #include @@ -28,7 +29,6 @@ #include -#include #include #include diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index 1af7acea36..5ecd3bd1fa 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -1,13 +1,7 @@ #include "animationbindings.hpp" -#include -#include -#include #include -#include #include -#include -#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -15,11 +9,8 @@ #include "../mwmechanics/character.hpp" -#include "../mwworld/esmstore.hpp" - #include "context.hpp" #include "luamanagerimp.hpp" -#include "objectvariant.hpp" namespace MWLua { diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp index 218d05b804..130144ab9b 100644 --- a/apps/openmw/mwlua/birthsignbindings.cpp +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -8,7 +8,7 @@ #include "../mwbase/environment.hpp" #include "idcollectionbindings.hpp" -#include "types/types.hpp" +#include "recordstore.hpp" namespace sol { diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index ec64a3cddd..4e4542d89f 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -1,7 +1,5 @@ #include "cellbindings.hpp" -#include - #include #include #include @@ -27,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -38,7 +35,6 @@ #include #include #include -#include #include #include #include diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index c94631fb3c..338698bfee 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -4,7 +4,7 @@ #include #include "idcollectionbindings.hpp" -#include "types/types.hpp" +#include "recordstore.hpp" namespace sol { diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index bdf71710af..653eef2a42 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index ad2913dd49..99c994304b 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -1,9 +1,5 @@ #include "localscripts.hpp" -#include -#include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/aicombat.hpp" diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index bc34576509..c51d2d8c19 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -1,9 +1,7 @@ #ifndef MWLUA_LOCALSCRIPTS_H #define MWLUA_LOCALSCRIPTS_H -#include -#include -#include +#include #include #include diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 850dd87eed..66ecfb1c55 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -5,6 +5,10 @@ #include #include +#include +#include +#include + #include #include @@ -15,7 +19,6 @@ #include -#include #include #include diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index bd4ab0d30e..42b18d236f 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -3,9 +3,10 @@ #include #include -#include #include +#include + #include #include #include diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 6c244a0fd4..230fab45db 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -16,6 +16,8 @@ #include "luamanagerimp.hpp" #include "objectlists.hpp" +#include + namespace { template diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index d032515314..4a50dee8b6 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -1,9 +1,7 @@ #ifndef MWLUA_OBJECT_H #define MWLUA_OBJECT_H -#include #include -#include #include diff --git a/apps/openmw/mwlua/objectlists.cpp b/apps/openmw/mwlua/objectlists.cpp index d0bda5a644..b0169dd651 100644 --- a/apps/openmw/mwlua/objectlists.cpp +++ b/apps/openmw/mwlua/objectlists.cpp @@ -1,9 +1,5 @@ #include "objectlists.hpp" -#include -#include -#include - #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwlua/racebindings.cpp b/apps/openmw/mwlua/racebindings.cpp index b5c4d6093f..590d16b4c9 100644 --- a/apps/openmw/mwlua/racebindings.cpp +++ b/apps/openmw/mwlua/racebindings.cpp @@ -8,7 +8,7 @@ #include "../mwworld/esmstore.hpp" #include "idcollectionbindings.hpp" -#include "types/types.hpp" +#include "recordstore.hpp" namespace { diff --git a/apps/openmw/mwlua/recordstore.hpp b/apps/openmw/mwlua/recordstore.hpp index aed84a1271..f2fb4ed453 100644 --- a/apps/openmw/mwlua/recordstore.hpp +++ b/apps/openmw/mwlua/recordstore.hpp @@ -1,10 +1,16 @@ #ifndef MWLUA_RECORDSTORE_H #define MWLUA_RECORDSTORE_H -#include +#include + +#include +#include +#include +#include +#include +#include +#include -#include -#include #include #include "apps/openmw/mwbase/environment.hpp" diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 637ae0b002..8b103d5943 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -1,9 +1,11 @@ #include "stats.hpp" #include -#include #include +#include #include +#include +#include #include #include diff --git a/apps/openmw/mwlua/userdataserializer.hpp b/apps/openmw/mwlua/userdataserializer.hpp index f52bb22723..35e4c4729b 100644 --- a/apps/openmw/mwlua/userdataserializer.hpp +++ b/apps/openmw/mwlua/userdataserializer.hpp @@ -1,7 +1,8 @@ #ifndef MWLUA_USERDATASERIALIZER_H #define MWLUA_USERDATASERIALIZER_H -#include "object.hpp" +#include +#include namespace LuaUtil { diff --git a/apps/openmw/mwlua/worldbindings.cpp b/apps/openmw/mwlua/worldbindings.cpp index c02bad3bd3..66c17699f2 100644 --- a/apps/openmw/mwlua/worldbindings.cpp +++ b/apps/openmw/mwlua/worldbindings.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include diff --git a/components/detournavigator/dbrefgeometryobject.hpp b/components/detournavigator/dbrefgeometryobject.hpp index dbbafe7e18..71e035d9e6 100644 --- a/components/detournavigator/dbrefgeometryobject.hpp +++ b/components/detournavigator/dbrefgeometryobject.hpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include diff --git a/components/detournavigator/generatenavmeshtile.cpp b/components/detournavigator/generatenavmeshtile.cpp index 118114181e..115ca75d40 100644 --- a/components/detournavigator/generatenavmeshtile.cpp +++ b/components/detournavigator/generatenavmeshtile.cpp @@ -10,10 +10,8 @@ #include -#include #include #include -#include #include namespace DetourNavigator @@ -79,7 +77,7 @@ namespace DetourNavigator return; } - const auto data + const std::unique_ptr data = prepareNavMeshTileData(*recastMesh, mWorldspace, mTilePosition, mAgentBounds, mSettings.mRecast); if (data == nullptr) diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index fe63d27a1e..06bb693aad 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -3,8 +3,6 @@ #include "exceptions.hpp" #include "flags.hpp" #include "navmeshdata.hpp" -#include "navmeshdb.hpp" -#include "navmeshtilescache.hpp" #include "offmeshconnection.hpp" #include "preparednavmeshdata.hpp" #include "recastcontext.hpp" @@ -22,8 +20,6 @@ #include #include -#include -#include namespace DetourNavigator { diff --git a/components/detournavigator/navmeshtileview.cpp b/components/detournavigator/navmeshtileview.cpp index d22efcbfff..e77962bb3c 100644 --- a/components/detournavigator/navmeshtileview.cpp +++ b/components/detournavigator/navmeshtileview.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include diff --git a/components/detournavigator/offmeshconnection.hpp b/components/detournavigator/offmeshconnection.hpp index fb8b166498..01bae02732 100644 --- a/components/detournavigator/offmeshconnection.hpp +++ b/components/detournavigator/offmeshconnection.hpp @@ -6,7 +6,6 @@ #include #include -#include namespace DetourNavigator { diff --git a/components/detournavigator/preparednavmeshdata.cpp b/components/detournavigator/preparednavmeshdata.cpp index a737ae19a5..32e559b8d0 100644 --- a/components/detournavigator/preparednavmeshdata.cpp +++ b/components/detournavigator/preparednavmeshdata.cpp @@ -4,8 +4,6 @@ #include -#include - namespace { void initPolyMeshDetail(rcPolyMeshDetail& value) noexcept diff --git a/components/detournavigator/raycast.hpp b/components/detournavigator/raycast.hpp index 855c5562fc..374b09320c 100644 --- a/components/detournavigator/raycast.hpp +++ b/components/detournavigator/raycast.hpp @@ -3,9 +3,10 @@ #include "flags.hpp" -#include #include +#include + class dtNavMeshQuery; namespace DetourNavigator diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 6d06db0799..1a67bb5495 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -11,9 +11,7 @@ #include #include -#include #include -#include #include #include diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 49fdd996a7..bef98b73f0 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -1,6 +1,7 @@ #include "configurationmanager.hpp" #include +#include #include #include diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 184c6ebb82..da4397146b 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -1,7 +1,6 @@ #ifndef COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP -#include #include #include #include diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 9fa01385d5..d456795781 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -27,7 +27,6 @@ #include #include -#include #include #include "glextensions.hpp" From eeca0b13b006fef6e0cdcdffd2b4fd1949046dab Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 2 Aug 2025 12:18:28 +0200 Subject: [PATCH 44/55] Add missing files to components/detournavigator list --- components/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 8a3325ea71..6b7fe32d40 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -407,6 +407,7 @@ add_component_dir(detournavigator areatype asyncnavmeshupdater bounds + cellgridbounds changetype collisionshapetype commulativeaabb From e87b95f642661f118321999d37a9716cae772080 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 20 Apr 2025 17:53:29 +0200 Subject: [PATCH 45/55] Use forward declaration for context and sol --- apps/openmw/mwlua/birthsignbindings.hpp | 4 ++-- apps/openmw/mwlua/cellbindings.hpp | 8 ++++---- apps/openmw/mwlua/classbindings.hpp | 4 ++-- apps/openmw/mwlua/corebindings.cpp | 1 + apps/openmw/mwlua/corebindings.hpp | 6 +++--- apps/openmw/mwlua/debugbindings.hpp | 2 +- apps/openmw/mwlua/factionbindings.hpp | 4 ++-- apps/openmw/mwlua/inputbindings.cpp | 1 + apps/openmw/mwlua/inputbindings.hpp | 6 +++--- apps/openmw/mwlua/landbindings.cpp | 10 ++++++++++ apps/openmw/mwlua/landbindings.hpp | 4 +++- apps/openmw/mwlua/luabindings.hpp | 17 +++++++++-------- apps/openmw/mwlua/magicbindings.hpp | 4 ++-- apps/openmw/mwlua/markupbindings.hpp | 6 +++--- apps/openmw/mwlua/menuscripts.cpp | 2 ++ apps/openmw/mwlua/menuscripts.hpp | 2 +- apps/openmw/mwlua/mwscriptbindings.cpp | 1 + apps/openmw/mwlua/mwscriptbindings.hpp | 6 ++---- apps/openmw/mwlua/nearbybindings.cpp | 1 + apps/openmw/mwlua/nearbybindings.hpp | 6 +++--- apps/openmw/mwlua/objectbindings.hpp | 8 ++++---- apps/openmw/mwlua/postprocessingbindings.cpp | 1 + apps/openmw/mwlua/postprocessingbindings.hpp | 6 +++--- apps/openmw/mwlua/racebindings.hpp | 4 ++-- apps/openmw/mwlua/soundbindings.hpp | 6 +++--- apps/openmw/mwlua/uibindings.hpp | 6 +++--- apps/openmw/mwlua/vfsbindings.hpp | 6 +++--- apps/openmw/mwlua/worldbindings.cpp | 1 + apps/openmw/mwlua/worldbindings.hpp | 6 +++--- 29 files changed, 79 insertions(+), 60 deletions(-) diff --git a/apps/openmw/mwlua/birthsignbindings.hpp b/apps/openmw/mwlua/birthsignbindings.hpp index bf41707d47..e57d56c971 100644 --- a/apps/openmw/mwlua/birthsignbindings.hpp +++ b/apps/openmw/mwlua/birthsignbindings.hpp @@ -3,10 +3,10 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; + sol::table initBirthSignRecordBindings(const Context& context); } diff --git a/apps/openmw/mwlua/cellbindings.hpp b/apps/openmw/mwlua/cellbindings.hpp index 0d8e90e989..70255d8d7f 100644 --- a/apps/openmw/mwlua/cellbindings.hpp +++ b/apps/openmw/mwlua/cellbindings.hpp @@ -1,12 +1,12 @@ #ifndef MWLUA_CELLBINDINGS_H #define MWLUA_CELLBINDINGS_H -#include "context.hpp" - namespace MWLua { - void initCellBindingsForLocalScripts(const Context&); - void initCellBindingsForGlobalScripts(const Context&); + struct Context; + + void initCellBindingsForLocalScripts(const Context& context); + void initCellBindingsForGlobalScripts(const Context& context); } #endif // MWLUA_CELLBINDINGS_H diff --git a/apps/openmw/mwlua/classbindings.hpp b/apps/openmw/mwlua/classbindings.hpp index 1acb0a9ad3..cc67f2df02 100644 --- a/apps/openmw/mwlua/classbindings.hpp +++ b/apps/openmw/mwlua/classbindings.hpp @@ -3,10 +3,10 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; + sol::table initClassRecordBindings(const Context& context); } diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 653eef2a42..282a9f970b 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -19,6 +19,7 @@ #include "../mwworld/datetimemanager.hpp" #include "../mwworld/esmstore.hpp" +#include "context.hpp" #include "coremwscriptbindings.hpp" #include "dialoguebindings.hpp" #include "factionbindings.hpp" diff --git a/apps/openmw/mwlua/corebindings.hpp b/apps/openmw/mwlua/corebindings.hpp index ef385ca993..6d27dd0412 100644 --- a/apps/openmw/mwlua/corebindings.hpp +++ b/apps/openmw/mwlua/corebindings.hpp @@ -3,13 +3,13 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; + void addCoreTimeBindings(sol::table& api, const Context& context); - sol::table initCorePackage(const Context&); + sol::table initCorePackage(const Context& context); } #endif // MWLUA_COREBINDINGS_H diff --git a/apps/openmw/mwlua/debugbindings.hpp b/apps/openmw/mwlua/debugbindings.hpp index c508b54496..d7902cb618 100644 --- a/apps/openmw/mwlua/debugbindings.hpp +++ b/apps/openmw/mwlua/debugbindings.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MWLUA_DEBUGBINDINGS_H #define OPENMW_MWLUA_DEBUGBINDINGS_H -#include +#include namespace MWLua { diff --git a/apps/openmw/mwlua/factionbindings.hpp b/apps/openmw/mwlua/factionbindings.hpp index 0dc06ceaf2..280c7d4791 100644 --- a/apps/openmw/mwlua/factionbindings.hpp +++ b/apps/openmw/mwlua/factionbindings.hpp @@ -3,10 +3,10 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; + sol::table initCoreFactionBindings(const Context& context); } diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index c3b47c5061..9a781106a6 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -13,6 +13,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwinput/actions.hpp" +#include "context.hpp" #include "luamanagerimp.hpp" namespace sol diff --git a/apps/openmw/mwlua/inputbindings.hpp b/apps/openmw/mwlua/inputbindings.hpp index 195f69f5f9..e9a19ed34d 100644 --- a/apps/openmw/mwlua/inputbindings.hpp +++ b/apps/openmw/mwlua/inputbindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initInputPackage(const Context&); + struct Context; + + sol::table initInputPackage(const Context& context); } #endif // MWLUA_INPUTBINDINGS_H diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 3f85e2b066..2b053802db 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -1,14 +1,24 @@ #include "landbindings.hpp" +#include +#include + +#include +#include +#include + #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/worldmodel.hpp" + +#include "context.hpp" #include "object.hpp" namespace diff --git a/apps/openmw/mwlua/landbindings.hpp b/apps/openmw/mwlua/landbindings.hpp index 8cdf30046b..f38d260759 100644 --- a/apps/openmw/mwlua/landbindings.hpp +++ b/apps/openmw/mwlua/landbindings.hpp @@ -1,10 +1,12 @@ #ifndef MWLUA_LANDBINDINGS_H #define MWLUA_LANDBINDINGS_H -#include "context.hpp" +#include namespace MWLua { + struct Context; + sol::table initCoreLandBindings(const Context& context); } diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index 749987e5b2..e7b73dbca1 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -1,29 +1,30 @@ #ifndef MWLUA_LUABINDINGS_H #define MWLUA_LUABINDINGS_H -#include #include -#include -#include "context.hpp" +#include +#include namespace MWLua { + struct Context; + // Initialize Lua packages that are available for all scripts. - std::map initCommonPackages(const Context&); + std::map initCommonPackages(const Context& context); // Initialize Lua packages that are available for global scripts (additionally to common packages). - std::map initGlobalPackages(const Context&); + std::map initGlobalPackages(const Context& context); // Initialize Lua packages that are available for local scripts (additionally to common packages). - std::map initLocalPackages(const Context&); + std::map initLocalPackages(const Context& context); // Initialize Lua packages that are available only for local scripts on the player (additionally to common and local // packages). - std::map initPlayerPackages(const Context&); + std::map initPlayerPackages(const Context& context); // Initialize Lua packages that are available only for menu scripts (additionally to common packages). - std::map initMenuPackages(const Context&); + std::map initMenuPackages(const Context& context); } #endif // MWLUA_LUABINDINGS_H diff --git a/apps/openmw/mwlua/magicbindings.hpp b/apps/openmw/mwlua/magicbindings.hpp index 047bd2e3d9..d9c5182d76 100644 --- a/apps/openmw/mwlua/magicbindings.hpp +++ b/apps/openmw/mwlua/magicbindings.hpp @@ -3,10 +3,10 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; + sol::table initCoreMagicBindings(const Context& context); void addActorMagicBindings(sol::table& actor, const Context& context); } diff --git a/apps/openmw/mwlua/markupbindings.hpp b/apps/openmw/mwlua/markupbindings.hpp index 9105ab5edf..ec4b6dbbb2 100644 --- a/apps/openmw/mwlua/markupbindings.hpp +++ b/apps/openmw/mwlua/markupbindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initMarkupPackage(const Context&); + struct Context; + + sol::table initMarkupPackage(const Context& context); } #endif // MWLUA_MARKUPBINDINGS_H diff --git a/apps/openmw/mwlua/menuscripts.cpp b/apps/openmw/mwlua/menuscripts.cpp index 32520c0822..a7ff80e63e 100644 --- a/apps/openmw/mwlua/menuscripts.cpp +++ b/apps/openmw/mwlua/menuscripts.cpp @@ -7,6 +7,8 @@ #include "../mwbase/statemanager.hpp" #include "../mwstate/character.hpp" +#include "context.hpp" + namespace MWLua { static const MWState::Character* findCharacter(std::string_view characterDir) diff --git a/apps/openmw/mwlua/menuscripts.hpp b/apps/openmw/mwlua/menuscripts.hpp index 8721224413..11c6cc1912 100644 --- a/apps/openmw/mwlua/menuscripts.hpp +++ b/apps/openmw/mwlua/menuscripts.hpp @@ -9,11 +9,11 @@ #include "../mwbase/luamanager.hpp" -#include "context.hpp" #include "inputprocessor.hpp" namespace MWLua { + struct Context; sol::table initMenuPackage(const Context& context); diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index 90d24b39fe..179b35aa5c 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -12,6 +12,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/worldimp.hpp" +#include "context.hpp" #include "object.hpp" #include diff --git a/apps/openmw/mwlua/mwscriptbindings.hpp b/apps/openmw/mwlua/mwscriptbindings.hpp index 9598f051ae..c0c88ec28c 100644 --- a/apps/openmw/mwlua/mwscriptbindings.hpp +++ b/apps/openmw/mwlua/mwscriptbindings.hpp @@ -3,13 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; - sol::table initMWScriptBindings(const Context&); - + sol::table initMWScriptBindings(const Context& context); } #endif // MWLUA_MWSCRIPTBINDINGS_H diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 230fab45db..b31a934a54 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -13,6 +13,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/scene.hpp" +#include "context.hpp" #include "luamanagerimp.hpp" #include "objectlists.hpp" diff --git a/apps/openmw/mwlua/nearbybindings.hpp b/apps/openmw/mwlua/nearbybindings.hpp index ee0022898e..2d8140195e 100644 --- a/apps/openmw/mwlua/nearbybindings.hpp +++ b/apps/openmw/mwlua/nearbybindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initNearbyPackage(const Context&); + struct Context; + + sol::table initNearbyPackage(const Context& context); } #endif // MWLUA_NEARBYBINDINGS_H diff --git a/apps/openmw/mwlua/objectbindings.hpp b/apps/openmw/mwlua/objectbindings.hpp index 5f69c54da3..3861211357 100644 --- a/apps/openmw/mwlua/objectbindings.hpp +++ b/apps/openmw/mwlua/objectbindings.hpp @@ -1,12 +1,12 @@ #ifndef MWLUA_OBJECTBINDINGS_H #define MWLUA_OBJECTBINDINGS_H -#include "context.hpp" - namespace MWLua { - void initObjectBindingsForLocalScripts(const Context&); - void initObjectBindingsForGlobalScripts(const Context&); + struct Context; + + void initObjectBindingsForLocalScripts(const Context& context); + void initObjectBindingsForGlobalScripts(const Context& context); } #endif // MWLUA_OBJECTBINDINGS_H diff --git a/apps/openmw/mwlua/postprocessingbindings.cpp b/apps/openmw/mwlua/postprocessingbindings.cpp index f12bda8650..6d0c33848a 100644 --- a/apps/openmw/mwlua/postprocessingbindings.cpp +++ b/apps/openmw/mwlua/postprocessingbindings.cpp @@ -8,6 +8,7 @@ #include "../mwbase/world.hpp" #include "../mwrender/postprocessor.hpp" +#include "context.hpp" #include "luamanagerimp.hpp" namespace diff --git a/apps/openmw/mwlua/postprocessingbindings.hpp b/apps/openmw/mwlua/postprocessingbindings.hpp index 50cd84fa24..55b46d2524 100644 --- a/apps/openmw/mwlua/postprocessingbindings.hpp +++ b/apps/openmw/mwlua/postprocessingbindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initPostprocessingPackage(const Context&); + struct Context; + + sol::table initPostprocessingPackage(const Context& context); } #endif // MWLUA_POSTPROCESSINGBINDINGS_H diff --git a/apps/openmw/mwlua/racebindings.hpp b/apps/openmw/mwlua/racebindings.hpp index 43ba9237c5..405a158899 100644 --- a/apps/openmw/mwlua/racebindings.hpp +++ b/apps/openmw/mwlua/racebindings.hpp @@ -3,10 +3,10 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; + sol::table initRaceRecordBindings(const Context& context); } diff --git a/apps/openmw/mwlua/soundbindings.hpp b/apps/openmw/mwlua/soundbindings.hpp index 333ed898c4..d7af17cb69 100644 --- a/apps/openmw/mwlua/soundbindings.hpp +++ b/apps/openmw/mwlua/soundbindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initCoreSoundBindings(const Context&); + struct Context; + + sol::table initCoreSoundBindings(const Context& context); sol::table initAmbientPackage(const Context& context); } diff --git a/apps/openmw/mwlua/uibindings.hpp b/apps/openmw/mwlua/uibindings.hpp index 930ba7f3d8..32fb54e590 100644 --- a/apps/openmw/mwlua/uibindings.hpp +++ b/apps/openmw/mwlua/uibindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initUserInterfacePackage(const Context&); + struct Context; + + sol::table initUserInterfacePackage(const Context& context); } #endif // MWLUA_UIBINDINGS_H diff --git a/apps/openmw/mwlua/vfsbindings.hpp b/apps/openmw/mwlua/vfsbindings.hpp index b251db6fd4..ab3ad3662c 100644 --- a/apps/openmw/mwlua/vfsbindings.hpp +++ b/apps/openmw/mwlua/vfsbindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initVFSPackage(const Context&); + struct Context; + + sol::table initVFSPackage(const Context& context); } #endif // MWLUA_VFSBINDINGS_H diff --git a/apps/openmw/mwlua/worldbindings.cpp b/apps/openmw/mwlua/worldbindings.cpp index 66c17699f2..d98c769e65 100644 --- a/apps/openmw/mwlua/worldbindings.cpp +++ b/apps/openmw/mwlua/worldbindings.cpp @@ -26,6 +26,7 @@ #include "luamanagerimp.hpp" #include "animationbindings.hpp" +#include "context.hpp" #include "corebindings.hpp" #include "mwscriptbindings.hpp" diff --git a/apps/openmw/mwlua/worldbindings.hpp b/apps/openmw/mwlua/worldbindings.hpp index 4bd2318b68..368344429b 100644 --- a/apps/openmw/mwlua/worldbindings.hpp +++ b/apps/openmw/mwlua/worldbindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initWorldPackage(const Context&); + struct Context; + + sol::table initWorldPackage(const Context& context); } #endif // MWLUA_WORLDBINDINGS_H From 7c4ed3c11d0063634dd61c86bf43949f09716db9 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Sat, 2 Aug 2025 15:19:49 -0700 Subject: [PATCH 46/55] Fixing XML? --- files/data/mygui/openmw_spellcreation_dialog.layout | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/files/data/mygui/openmw_spellcreation_dialog.layout b/files/data/mygui/openmw_spellcreation_dialog.layout index 4dc895d26a..d616e00dcb 100644 --- a/files/data/mygui/openmw_spellcreation_dialog.layout +++ b/files/data/mygui/openmw_spellcreation_dialog.layout @@ -73,9 +73,7 @@ - - - + From 2abed374f9d600cdfc3584707468053c6054fd93 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Sat, 2 Aug 2025 19:00:12 -0700 Subject: [PATCH 47/55] Fix missing link --- docs/source/reference/lua-scripting/index_interfaces.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/reference/lua-scripting/index_interfaces.rst b/docs/source/reference/lua-scripting/index_interfaces.rst index 37a2df63c4..0b113c7744 100644 --- a/docs/source/reference/lua-scripting/index_interfaces.rst +++ b/docs/source/reference/lua-scripting/index_interfaces.rst @@ -11,6 +11,7 @@ Interfaces AI AnimationController Camera + Combat Controls Crimes GamepadControls From 286c4d9101bf360fd75561ff58d452d1e8679755 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Sat, 2 Aug 2025 21:40:09 -0700 Subject: [PATCH 48/55] a word --- docs/source/reference/lua-scripting/events.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index 590ff14d88..02eff68f8c 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -108,7 +108,7 @@ Example: attacker = self, weapon = Actor.getEquipment(self, Actor.EQUIPMENT_SLOT.CarriedRight), sourceType = I.Combat.ATTACK_SOURCE_TYPE.Melee, - strenght = 1, + strength = 1, type = self.ATTACK_TYPE.Chop, damage = { health = 20, From c276f850dc9ca47f5df49b052f4171a9be4f0470 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Sat, 2 Aug 2025 22:49:58 -0700 Subject: [PATCH 49/55] One more fix --- docs/source/reference/lua-scripting/events.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index 02eff68f8c..810739caeb 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -107,7 +107,7 @@ Example: local attack = { attacker = self, weapon = Actor.getEquipment(self, Actor.EQUIPMENT_SLOT.CarriedRight), - sourceType = I.Combat.ATTACK_SOURCE_TYPE.Melee, + sourceType = I.Combat.ATTACK_SOURCE_TYPES.Melee, strength = 1, type = self.ATTACK_TYPE.Chop, damage = { From 555a10f50c460a418316a1b20d1f2e8ab162889c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 3 Aug 2025 14:11:38 +0200 Subject: [PATCH 50/55] Fix msvc warnings openmw\apps\opencs\model\world\idcollection.cpp(65): warning C4457: declaration of 'index' hides function parameter openmw\apps\openmw\mwscript\miscextensions.cpp(627): warning C4456: declaration of 'effect' hides previous local declaration --- apps/opencs/model/world/idcollection.cpp | 15 +++++++-------- apps/openmw/mwscript/miscextensions.cpp | 6 +++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/opencs/model/world/idcollection.cpp b/apps/opencs/model/world/idcollection.cpp index ca56eceb69..7890dc47b8 100644 --- a/apps/opencs/model/world/idcollection.cpp +++ b/apps/opencs/model/world/idcollection.cpp @@ -59,14 +59,13 @@ namespace CSMWorld const Record* IdCollection::searchRecord(std::uint16_t index, int plugin) const { - auto found = mIndices.find({ plugin, index }); - if (found != mIndices.end()) - { - int index = searchId(found->second); - if (index != -1) - return &getRecord(index); - } - return nullptr; + const auto it = mIndices.find({ plugin, index }); + if (it == mIndices.end()) + return nullptr; + const int recordIndex = searchId(it->second); + if (recordIndex == -1) + return nullptr; + return &getRecord(recordIndex); } const std::string* IdCollection::getLandTexture(std::uint16_t index, int plugin) const diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index fd2064463e..1511909332 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -604,7 +604,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - std::string_view effect = runtime.getStringLiteral(runtime[0].mInteger); + const std::string_view effectName = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); if (!ptr.getClass().isActor()) @@ -615,11 +615,11 @@ namespace MWScript long key; - if (const auto k = ::Misc::StringUtils::toNumeric(effect); + if (const auto k = ::Misc::StringUtils::toNumeric(effectName); k.has_value() && *k >= 0 && *k <= 32767) key = *k; else - key = ESM::MagicEffect::effectGmstIdToIndex(effect); + key = ESM::MagicEffect::effectGmstIdToIndex(effectName); const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); for (const auto& spell : stats.getActiveSpells()) From 7569273b33f8398439c629f468efab4cc0d9bf35 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 3 Aug 2025 17:06:47 +0200 Subject: [PATCH 51/55] Expose cov-int.tar.gz as an artifact and upload its URL --- .gitlab-ci.yml | 22 ++++++++++++++++------ CI/install_debian_deps.sh | 1 + 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0d87ed9e03..ca36ba0f4d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -125,18 +125,28 @@ Coverity: - ccache -z -M "${CCACHE_SIZE}" - CI/before_script.linux.sh - cov-analysis-linux64-*/bin/cov-configure --template --comptype prefix --compiler ccache - # Remove the specific targets and build everything once we can do it under 3h - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) - ccache -svv - after_script: - tar cfz cov-int.tar.gz cov-int - - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME - --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL - --form file=@cov-int.tar.gz --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA" - --form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" artifacts: + expire_in: 1 day paths: - /builds/OpenMW/openmw/cov-int/build-log.txt + - /builds/OpenMW/openmw/cov-int.tar.gz + +Coverity_Upload: + image: ubuntu:24.04 + stage: build + before_script: + - CI/install_debian_deps.sh coverity_upload + script: + - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME + --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL + --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA" + --form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" + --form url="$CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/artifacts/$CI_DEFAULT_BRANCH_SLUG/raw/cov-int.tar.gz?job=Coverity" + needs: + - Coverity Ubuntu_GCC: extends: .Ubuntu diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index d4a7e88ed6..aada722fb1 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -12,6 +12,7 @@ declare -rA GROUPED_DEPS=( [gcc]="binutils gcc build-essential cmake ccache curl unzip git pkg-config mold" [clang]="binutils clang make cmake ccache curl unzip git pkg-config mold" [coverity]="binutils clang make cmake ccache curl unzip git pkg-config file" + [coverity_upload]="curl" [gcc_preprocess]=" binutils build-essential From b6b4e4f1020ccdb8ca4abee737311e956f881251 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 3 Aug 2025 18:36:30 +0200 Subject: [PATCH 52/55] Use a URL that doesn't require the pipeline to succeed --- .gitlab-ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ca36ba0f4d..26497649d1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -128,11 +128,14 @@ Coverity: - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) - ccache -svv - tar cfz cov-int.tar.gz cov-int + - echo "OPENMW_JOB_ID=$CI_JOB_ID" >> build.env artifacts: expire_in: 1 day paths: - /builds/OpenMW/openmw/cov-int/build-log.txt - /builds/OpenMW/openmw/cov-int.tar.gz + reports: + dotenv: build.env Coverity_Upload: image: ubuntu:24.04 @@ -140,11 +143,12 @@ Coverity_Upload: before_script: - CI/install_debian_deps.sh coverity_upload script: + - echo "$CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/$OPENMW_JOB_ID/artifacts/cov-int.tar.gz" - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA" --form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" - --form url="$CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/artifacts/$CI_DEFAULT_BRANCH_SLUG/raw/cov-int.tar.gz?job=Coverity" + --form url="$CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/$OPENMW_JOB_ID/artifacts/cov-int.tar.gz" needs: - Coverity From 3bf62235ba968186d18d511f30f91bbabea5ac28 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 3 Aug 2025 20:05:36 +0200 Subject: [PATCH 53/55] Restore rules --- .gitlab-ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 26497649d1..cdda455dac 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -99,8 +99,8 @@ Coverity: - saas-linux-medium-amd64 image: ubuntu:24.04 stage: build -# rules: -# - if: $CI_PIPELINE_SOURCE == "schedule" + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" cache: key: Coverity.ubuntu_24.04.v1 paths: @@ -140,6 +140,8 @@ Coverity: Coverity_Upload: image: ubuntu:24.04 stage: build + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" before_script: - CI/install_debian_deps.sh coverity_upload script: From 0d7efaa2e3895d7d6664c93fa7ecb97d7237ca3b Mon Sep 17 00:00:00 2001 From: zlice Date: Wed, 16 Jul 2025 09:00:50 -0400 Subject: [PATCH 54/55] fix 32bit builds --- components/esm/formid.hpp | 8 ++++---- components/misc/strings/algorithm.hpp | 18 ++++++++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/components/esm/formid.hpp b/components/esm/formid.hpp index e9416e35c7..9fe89585c2 100644 --- a/components/esm/formid.hpp +++ b/components/esm/formid.hpp @@ -51,10 +51,10 @@ namespace std { size_t operator()(const ESM::FormId& formId) const { - static_assert(sizeof(ESM::FormId) == sizeof(size_t)); - size_t s; - memcpy(&s, &formId, sizeof(size_t)); - return hash()(s); + static_assert(sizeof(ESM::FormId) == sizeof(uint64_t)); + uint64_t s; + memcpy(&s, &formId, sizeof(ESM::FormId)); + return hash()(s); } }; diff --git a/components/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index 18f72104bd..9b42588780 100644 --- a/components/misc/strings/algorithm.hpp +++ b/components/misc/strings/algorithm.hpp @@ -4,6 +4,7 @@ #include "lower.hpp" #include +#include #include #include #include @@ -88,14 +89,23 @@ namespace Misc::StringUtils constexpr std::size_t operator()(std::string_view str) const { // FNV-1a - std::size_t hash{ 0xcbf29ce484222325ull }; - constexpr std::size_t prime{ 0x00000100000001B3ull }; + std::uint64_t hash{ 0xcbf29ce484222325ull }; + constexpr std::uint64_t prime{ 0x00000100000001B3ull }; for (char c : str) { - hash ^= static_cast(toLower(c)); + hash ^= static_cast(toLower(c)); hash *= prime; } - return hash; + if constexpr (sizeof(std::uint64_t) <= sizeof(std::size_t)) + return hash; + else if constexpr (sizeof(std::uint32_t) == sizeof(std::size_t)) + { + std::uint32_t low = hash & 0xFFFFFFFF; + std::uint32_t high = hash >> 32; + return low ^ high; + } + static_assert(sizeof(std::uint64_t) <= sizeof(std::size_t) || sizeof(std::uint32_t) == sizeof(std::size_t)); + return {}; } }; From 485517cf64c83c86f398b3df0d8cc3c1ce6566a5 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 3 Aug 2025 20:52:16 +0200 Subject: [PATCH 55/55] Use std::hash --- components/misc/strings/algorithm.hpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/components/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index 9b42588780..681bc0607a 100644 --- a/components/misc/strings/algorithm.hpp +++ b/components/misc/strings/algorithm.hpp @@ -86,7 +86,7 @@ namespace Misc::StringUtils { using is_transparent = void; - constexpr std::size_t operator()(std::string_view str) const + std::size_t operator()(std::string_view str) const { // FNV-1a std::uint64_t hash{ 0xcbf29ce484222325ull }; @@ -96,16 +96,7 @@ namespace Misc::StringUtils hash ^= static_cast(toLower(c)); hash *= prime; } - if constexpr (sizeof(std::uint64_t) <= sizeof(std::size_t)) - return hash; - else if constexpr (sizeof(std::uint32_t) == sizeof(std::size_t)) - { - std::uint32_t low = hash & 0xFFFFFFFF; - std::uint32_t high = hash >> 32; - return low ^ high; - } - static_assert(sizeof(std::uint64_t) <= sizeof(std::size_t) || sizeof(std::uint32_t) == sizeof(std::size_t)); - return {}; + return std::hash()(hash); } };