From 29177bccb8dac4458330f7c0294cc4eb2e9f076f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 21 Feb 2025 00:56:09 +0300 Subject: [PATCH 01/26] Play the item's down sound when an item quick key is assigned --- apps/openmw/mwgui/quickkeysmenu.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index c8932c97b6..7e501071e4 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -246,6 +246,8 @@ namespace MWGui if (mItemSelectionDialog) mItemSelectionDialog->setVisible(false); + + MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item)); } void QuickKeysMenu::onAssignItemCancel() From 982962c6086b553d6746dfd971e0318a6cbab941 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 21 Feb 2025 00:56:37 +0300 Subject: [PATCH 02/26] Don't center quick keys menu instructions --- files/data/mygui/openmw_quickkeys_menu.layout | 1 - 1 file changed, 1 deletion(-) diff --git a/files/data/mygui/openmw_quickkeys_menu.layout b/files/data/mygui/openmw_quickkeys_menu.layout index ba6832cf95..892d4e748b 100644 --- a/files/data/mygui/openmw_quickkeys_menu.layout +++ b/files/data/mygui/openmw_quickkeys_menu.layout @@ -12,7 +12,6 @@ - From 733dfbb89d938ebb1c9539205a5713227374fdc1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 21 Feb 2025 01:00:17 +0300 Subject: [PATCH 03/26] Don't assume any scripted item is usable --- apps/openmw/mwgui/sortfilteritemmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index fe85ea4bd0..8c6277db4d 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -279,7 +279,7 @@ namespace MWGui && !base.get()->mBase->mData.mIsScroll) return false; - if ((mFilter & Filter_OnlyUsableItems) && base.getClass().getScript(base).empty()) + if ((mFilter & Filter_OnlyUsableItems)) { std::unique_ptr actionOnUse = base.getClass().use(base); if (!actionOnUse || actionOnUse->isNullAction()) From a0e0b3c65b8610365602988372430f21b3abd0d7 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 3 Jul 2025 08:30:50 +0300 Subject: [PATCH 04/26] Some quick key menu layout fixes --- files/data/mygui/openmw_quickkeys_menu.layout | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/files/data/mygui/openmw_quickkeys_menu.layout b/files/data/mygui/openmw_quickkeys_menu.layout index 892d4e748b..697f186411 100644 --- a/files/data/mygui/openmw_quickkeys_menu.layout +++ b/files/data/mygui/openmw_quickkeys_menu.layout @@ -1,35 +1,35 @@ - + - + - + - + - - - - - + + + + + - + From 266702e729b4351f60a458ad58c345abb68dc0c6 Mon Sep 17 00:00:00 2001 From: Aussiemon Date: Tue, 15 Apr 2025 00:42:22 -0600 Subject: [PATCH 05/26] Remove obsolete combat block for immobile creatures and autoformat --- apps/openmw/mwmechanics/actors.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 1e62cc4a21..01e5c21559 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -184,7 +184,7 @@ namespace MWWorld::ContainerStoreIterator gem = container.end(); float gemCapacity = std::numeric_limits::max(); for (auto it = container.begin(MWWorld::ContainerStore::Type_Miscellaneous); it != container.end(); - ++it) + ++it) { if (it->getClass().isSoulGem(*it)) { @@ -605,10 +605,6 @@ namespace MWMechanics void Actors::engageCombat( const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, SidingCache& cachedAllies, bool againstPlayer) const { - // No combat for totally static creatures - if (!actor1.getClass().isMobile(actor1)) - return; - CreatureStats& creatureStats1 = actor1.getClass().getCreatureStats(actor1); if (creatureStats1.isDead() || creatureStats1.getAiSequence().isInCombat(actor2)) return; @@ -1153,8 +1149,7 @@ namespace MWMechanics if (playerStats.getBounty() >= cutoff * iCrimeThresholdMultiplier) { mechanicsManager->startCombat(ptr, player, &cachedAllies.getActorsSidingWith(player)); - creatureStats.setHitAttemptActorId( - playerClass.getCreatureStats(player) + creatureStats.setHitAttemptActorId(playerClass.getCreatureStats(player) .getActorId()); // Stops the guard from quitting combat if player is unreachable } else From a77a8904e599207a58a5ebabf550259713857800 Mon Sep 17 00:00:00 2001 From: Aussiemon Date: Tue, 15 Apr 2025 17:25:58 -0600 Subject: [PATCH 06/26] Immobile creatures should enter combat, but not act --- apps/openmw/mwmechanics/aicombat.cpp | 8 +++++--- apps/openmw/mwmechanics/aipackage.cpp | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 2399961a3a..a96270c2e3 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -104,10 +104,10 @@ namespace MWMechanics bool AiCombat::execute( const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { - // get or create temporary storage + // Get or create temporary storage AiCombatStorage& storage = state.get(); - // General description + // No combat for dead creatures if (actor.getClass().getCreatureStats(actor).isDead()) return true; @@ -245,7 +245,9 @@ namespace MWMechanics float distToTarget = getDistanceToBounds(actor, target); - storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS); + // Must be attacking, within range, have line of sight, and not be immobile + storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS + && actor.getClass().isMobile(actor)); if (isRangedCombat) { diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 4bcfc7dedd..24792b5bbc 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -135,6 +135,10 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& MWWorld::MovementDirectionFlags supportedMovementDirections, float destTolerance, float endTolerance, PathType pathType) { + // No pathing for totally static creatures + if (!actor.getClass().isMobile(actor)) + return false; + const Misc::TimerStatus timerStatus = mReaction.update(duration); const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); // position of the actor From 23dc226dff12e0576389d2735259cb61ce121ed7 Mon Sep 17 00:00:00 2001 From: Aussiemon Date: Tue, 15 Apr 2025 17:56:48 -0600 Subject: [PATCH 07/26] Revert autoformat changes --- apps/openmw/mwmechanics/actors.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 01e5c21559..a4c2f42f0f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -184,7 +184,7 @@ namespace MWWorld::ContainerStoreIterator gem = container.end(); float gemCapacity = std::numeric_limits::max(); for (auto it = container.begin(MWWorld::ContainerStore::Type_Miscellaneous); it != container.end(); - ++it) + ++it) { if (it->getClass().isSoulGem(*it)) { @@ -1149,7 +1149,8 @@ namespace MWMechanics if (playerStats.getBounty() >= cutoff * iCrimeThresholdMultiplier) { mechanicsManager->startCombat(ptr, player, &cachedAllies.getActorsSidingWith(player)); - creatureStats.setHitAttemptActorId(playerClass.getCreatureStats(player) + creatureStats.setHitAttemptActorId( + playerClass.getCreatureStats(player) .getActorId()); // Stops the guard from quitting combat if player is unreachable } else From 3df695df4a0f393a534736b558dc02cad983bcf7 Mon Sep 17 00:00:00 2001 From: Aussiemon Date: Tue, 15 Apr 2025 18:38:53 -0600 Subject: [PATCH 08/26] Found better place for early return --- apps/openmw/mwmechanics/aicombat.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index a96270c2e3..82fc9a67a1 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -231,6 +231,10 @@ namespace MWMechanics storage.stopFleeing(); } + // No attack actions for totally static creatures + if (!actor.getClass().isMobile(actor)) + return false; + bool isRangedCombat = false; float& rangeAttack = storage.mAttackRange; @@ -245,9 +249,7 @@ namespace MWMechanics float distToTarget = getDistanceToBounds(actor, target); - // Must be attacking, within range, have line of sight, and not be immobile - storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS - && actor.getClass().isMobile(actor)); + storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS); if (isRangedCombat) { From 09ed5bb234212484da5294ddfab723495edc62b4 Mon Sep 17 00:00:00 2001 From: Aussiemon Date: Tue, 15 Apr 2025 20:02:24 -0600 Subject: [PATCH 09/26] Allow immobile actors to initiate combat when attacked --- apps/openmw/mwclass/creature.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 324ba5f98b..b85bb9e996 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -360,16 +360,12 @@ namespace MWClass { stats.setAttacked(true); - // No retaliation for totally static creatures (they have no movement or attacks anyway) - if (isMobile(ptr)) - { - bool complain = sourceType == MWMechanics::DamageSourceType::Melee; - bool supportFriendlyFire = sourceType != MWMechanics::DamageSourceType::Ranged; - if (supportFriendlyFire && MWMechanics::friendlyHit(attacker, ptr, complain)) - setOnPcHitMe = false; - else - setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); - } + bool complain = sourceType == MWMechanics::DamageSourceType::Melee; + bool supportFriendlyFire = sourceType != MWMechanics::DamageSourceType::Ranged; + if (supportFriendlyFire && MWMechanics::friendlyHit(attacker, ptr, complain)) + setOnPcHitMe = false; + else + setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); } // Attacker and target store each other as hitattemptactor if they have no one stored yet From 2db36c49b5d6cd6dd129637e3fb5c06ca2b6f438 Mon Sep 17 00:00:00 2001 From: Aussiemon Date: Sun, 6 Jul 2025 11:41:02 -0600 Subject: [PATCH 10/26] Move early out to better place --- apps/openmw/mwmechanics/aicombat.cpp | 11 +++++++---- apps/openmw/mwmechanics/aipackage.cpp | 4 ---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 82fc9a67a1..1b0b0e5a89 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -124,6 +124,13 @@ namespace MWMechanics if (actor == target) // This should never happen. return true; + // No actions for totally static creatures + if (!actor.getClass().isMobile(actor)) + { + storage.mFleeState = AiCombatStorage::FleeState_Idle; + return false; + } + if (!storage.isFleeing()) { if (storage.mCurrentAction.get()) // need to wait to init action with its attack range @@ -231,10 +238,6 @@ namespace MWMechanics storage.stopFleeing(); } - // No attack actions for totally static creatures - if (!actor.getClass().isMobile(actor)) - return false; - bool isRangedCombat = false; float& rangeAttack = storage.mAttackRange; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 24792b5bbc..4bcfc7dedd 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -135,10 +135,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& MWWorld::MovementDirectionFlags supportedMovementDirections, float destTolerance, float endTolerance, PathType pathType) { - // No pathing for totally static creatures - if (!actor.getClass().isMobile(actor)) - return false; - const Misc::TimerStatus timerStatus = mReaction.update(duration); const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); // position of the actor From c4a4c435f1b49c7ff9f23bbc3e6743455fe2dcfc Mon Sep 17 00:00:00 2001 From: Kindi Date: Fri, 4 Apr 2025 11:53:55 +0800 Subject: [PATCH 11/26] refactor opshowvars --- apps/openmw/mwscript/miscextensions.cpp | 91 +++++++++++-------------- 1 file changed, 38 insertions(+), 53 deletions(-) diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 06abbe5bb2..af81fd2d3c 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1146,9 +1146,18 @@ namespace MWScript { void printLocalVars(Interpreter::Runtime& runtime, const MWWorld::Ptr& ptr) { - std::stringstream str; + std::ostringstream str; const ESM::RefId& script = ptr.getClass().getScript(ptr); + + auto printVariables = [&str](const auto& names, const auto& values, std::string_view type) { + size_t size = std::min(names.size(), values.size()); + for (size_t i = 0; i < size; ++i) + { + str << "\n " << names[i] << " = " << values[i] << " (" << type << ")"; + } + }; + if (script.empty()) str << ptr.getCellRef().getRefId() << " does not have a script."; else @@ -1159,27 +1168,9 @@ namespace MWScript const Compiler::Locals& complocals = MWBase::Environment::get().getScriptManager()->getLocals(script); - const std::vector* names = &complocals.get('s'); - for (size_t i = 0; i < names->size(); ++i) - { - if (i >= locals.mShorts.size()) - break; - str << std::endl << " " << (*names)[i] << " = " << locals.mShorts[i] << " (short)"; - } - names = &complocals.get('l'); - for (size_t i = 0; i < names->size(); ++i) - { - if (i >= locals.mLongs.size()) - break; - str << std::endl << " " << (*names)[i] << " = " << locals.mLongs[i] << " (long)"; - } - names = &complocals.get('f'); - for (size_t i = 0; i < names->size(); ++i) - { - if (i >= locals.mFloats.size()) - break; - str << std::endl << " " << (*names)[i] << " = " << locals.mFloats[i] << " (float)"; - } + printVariables(complocals.get('s'), locals.mShorts, "short"); + printVariables(complocals.get('l'), locals.mLongs, "long"); + printVariables(complocals.get('f'), locals.mFloats, "float"); } runtime.getContext().report(str.str()); @@ -1187,50 +1178,43 @@ namespace MWScript void printGlobalVars(Interpreter::Runtime& runtime) { - std::stringstream str; - str << "Global variables:"; + std::ostringstream str; + str << "Global Variables:"; MWBase::World* world = MWBase::Environment::get().getWorld(); - std::vector names = runtime.getContext().getGlobals(); + auto& context = runtime.getContext(); + std::vector names = context.getGlobals(); - // sort for user convenience - std::sort(names.begin(), names.end()); - - for (size_t i = 0; i < names.size(); ++i) - { - char type = world->getGlobalVariableType(names[i]); - str << std::endl << " " << names[i] << " = "; + std::sort(names.begin(), names.end(), ::Misc::StringUtils::ciLess); + auto printVariable = [&str, &context](const std::string& name, char type) { + str << "\n " << name << " = "; switch (type) { case 's': - - str << runtime.getContext().getGlobalShort(names[i]) << " (short)"; + str << context.getGlobalShort(name) << " (short)"; break; - case 'l': - - str << runtime.getContext().getGlobalLong(names[i]) << " (long)"; + str << context.getGlobalLong(name) << " (long)"; break; - case 'f': - - str << runtime.getContext().getGlobalFloat(names[i]) << " (float)"; + str << context.getGlobalFloat(name) << " (float)"; break; - default: - str << ""; } - } + }; - runtime.getContext().report(str.str()); + for (const auto& name : names) + printVariable(name, world->getGlobalVariableType(name)); + + context.report(str.str()); } void printGlobalScriptsVars(Interpreter::Runtime& runtime) { - std::stringstream str; - str << std::endl << "Global Scripts:"; + std::ostringstream str; + str << "\nGlobal Scripts:"; const auto& scripts = MWBase::Environment::get().getScriptManager()->getGlobalScripts().getScripts(); @@ -1238,12 +1222,11 @@ namespace MWScript std::map> globalScripts(scripts.begin(), scripts.end()); auto printVariables - = [&str](const ESM::RefId& scptName, const auto& names, const auto& values, std::string_view type) { + = [&str](std::string_view scptName, const auto& names, const auto& values, std::string_view type) { size_t size = std::min(names.size(), values.size()); for (size_t i = 0; i < size; ++i) { - str << std::endl - << " " << scptName << "->" << names[i] << " = " << values[i] << " (" << type << ")"; + str << "\n " << scptName << "->" << names[i] << " = " << values[i] << " (" << type << ")"; } }; @@ -1253,18 +1236,20 @@ namespace MWScript if (!script->mRunning) continue; + std::string_view scptName = refId.getRefIdString(); + const Compiler::Locals& complocals = MWBase::Environment::get().getScriptManager()->getLocals(refId); const Locals& locals = MWBase::Environment::get().getScriptManager()->getGlobalScripts().getLocals(refId); if (locals.isEmpty()) - str << std::endl << " No variables in script " << refId; + str << "\n No variables in script " << scptName; else { - printVariables(refId, complocals.get('s'), locals.mShorts, "short"); - printVariables(refId, complocals.get('l'), locals.mLongs, "long"); - printVariables(refId, complocals.get('f'), locals.mFloats, "float"); + printVariables(scptName, complocals.get('s'), locals.mShorts, "short"); + printVariables(scptName, complocals.get('l'), locals.mLongs, "long"); + printVariables(scptName, complocals.get('f'), locals.mFloats, "float"); } } From 40e9b2d707f432616717d296aa38876440c52781 Mon Sep 17 00:00:00 2001 From: Kuyondo Date: Tue, 27 May 2025 01:48:58 +0800 Subject: [PATCH 12/26] play down sound when equip fails --- apps/openmw/mwgui/inventorywindow.cpp | 63 +++++++++++---------------- apps/openmw/mwgui/quickkeysmenu.cpp | 32 +++++++++----- 2 files changed, 46 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 410ed31a66..017616295f 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -522,33 +522,15 @@ namespace MWGui } MWWorld::Ptr player = MWMechanics::getPlayer(); + bool canUse = true; - // early-out for items that need to be equipped, but can't be equipped: we don't want to set OnPcEquip in that - // case - if (!ptr.getClass().getEquipmentSlots(ptr).first.empty()) - { - if (ptr.getClass().hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}"); - updateItemView(); - return; - } - - if (!force) - { - auto canEquip = ptr.getClass().canBeEquipped(ptr, player); - - if (canEquip.first == 0) - { - MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second); - updateItemView(); - return; - } - } - } + // We don't want to set OnPcEquip for items that need to be equipped; but cannot be equipped; + if (!ptr.getClass().getEquipmentSlots(ptr).first.empty() + && ptr.getClass().canBeEquipped(ptr, player).first == 0) + canUse = force && ptr.getClass().hasItemHealth(ptr) && ptr.getCellRef().getCharge() != 0; // If the item has a script, set OnPCEquip or PCSkipEquip to 1 - if (!script.empty()) + if (!script.empty() && canUse) { // Ingredients, books and repair hammers must not have OnPCEquip set to 1 here auto type = ptr.getType(); @@ -561,7 +543,25 @@ namespace MWGui } std::unique_ptr action = ptr.getClass().use(ptr, force); - action->execute(player); + + action->execute(player, !canUse); + + if (mDragAndDrop->mIsOnDragAndDrop && mDragAndDrop->mItem.mBase == ptr) + { + if (canUse) + { + mDragAndDrop->finish(); + // If item is ingredient or potion don't stop drag and drop + if ((ptr.getType() == ESM::Potion::sRecordId || ptr.getType() == ESM::Ingredient::sRecordId) + && mDragAndDrop->mDraggedCount > 1) + { + mSelectedItem = getModel()->getIndex(mDragAndDrop->mItem); + dragItem(nullptr, mDragAndDrop->mDraggedCount - 1); + } + } + else + mDragAndDrop->drop(mTradeModel, mItemView); + } // Handles partial equipping (final part) if (mEquippedStackableCount.has_value()) @@ -592,8 +592,6 @@ namespace MWGui { MWWorld::Ptr ptr = mDragAndDrop->mItem.mBase; - mDragAndDrop->finish(); - if (mDragAndDrop->mSourceModel != mTradeModel) { // Move item to the player's inventory @@ -617,17 +615,6 @@ namespace MWGui } MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer(), false); - - // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 - // item - if ((ptr.getType() == ESM::Potion::sRecordId || ptr.getType() == ESM::Ingredient::sRecordId) - && mDragAndDrop->mDraggedCount > 1) - { - // Item can be provided from other window for example container. - // But after DragAndDrop::startDrag item automaticly always gets to player inventory. - mSelectedItem = getModel()->getIndex(mDragAndDrop->mItem); - dragItem(nullptr, mDragAndDrop->mDraggedCount - 1); - } } else { diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index c8932c97b6..deb844078b 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -84,9 +84,8 @@ namespace MWGui case ESM::QuickKeys::Type::MagicItem: { MWWorld::Ptr item = *mKey[index].button->getUserData(); - // Make sure the item is available and is not broken - if (item.isEmpty() || item.getCellRef().getCount() < 1 - || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) + // Make sure the item is available + if (item.isEmpty() || item.getCellRef().getCount() < 1) { // Try searching for a compatible replacement item = store.findReplacement(mKey[index].id); @@ -382,18 +381,29 @@ namespace MWGui if (it == store.end()) item = nullptr; - // check the item is available and not broken - if (item.isEmpty() || item.getCellRef().getCount() < 1 - || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) + // check the quickkey item is available + if (item.isEmpty() || item.getCellRef().getCount() < 1) { - item = store.findReplacement(key->id); + MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + key->name); + return; + } - if (item.isEmpty() || item.getCellRef().getCount() < 1) + // check the quickkey item is not broken + if (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0) + { + const std::vector& equipmentSlots = item.getClass().getEquipmentSlots(item).first; + if (!equipmentSlots.empty()) { - MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + key->name); - - return; + const auto& itSlot = store.getSlot(equipmentSlots.front()); + // Morrowind.exe behaviour: + // Only display item broken message if; + // no item in the to-be-equipped slot + // or broken quickkey item and currently equipped item id is different + // It doesn't find a replacement + if (itSlot == store.end() || (item.getCellRef().getRefId() != itSlot->getCellRef().getRefId())) + MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}"); } + return; } if (key->type == ESM::QuickKeys::Type::Item) From e4f0723a90efe1a2661dea6fc0db2d0c1ead1a1b Mon Sep 17 00:00:00 2001 From: Kuyondo Date: Tue, 27 May 2025 15:50:23 +0800 Subject: [PATCH 13/26] cleanup --- apps/openmw/mwclass/weapon.cpp | 6 +-- apps/openmw/mwgui/inventorywindow.cpp | 62 ++++++++++++++++----------- apps/openmw/mwgui/quickkeysmenu.cpp | 9 ++-- apps/openmw/mwworld/actionequip.cpp | 9 ++-- 4 files changed, 47 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 089c8c2894..3524446b26 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -270,14 +270,14 @@ namespace MWClass std::pair Weapon::canBeEquipped(const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const { - if (hasItemHealth(ptr) && getItemHealth(ptr) == 0) - return { 0, "#{sInventoryMessage1}" }; - // Do not allow equip weapons from inventory during attack if (npc.isInCell() && MWBase::Environment::get().getWindowManager()->isGuiMode() && MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)) return { 0, "#{sCantEquipWeapWarning}" }; + if (hasItemHealth(ptr) && getItemHealth(ptr) == 0) + return { 0, "#{sInventoryMessage1}" }; + std::pair, bool> slots_ = getEquipmentSlots(ptr); if (slots_.first.empty()) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 017616295f..1f6b6fdeb5 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -522,18 +522,25 @@ namespace MWGui } MWWorld::Ptr player = MWMechanics::getPlayer(); - bool canUse = true; + auto type = ptr.getType(); + bool isWeaponOrArmor = type == ESM::Weapon::sRecordId || type == ESM::Armor::sRecordId; + bool isBroken = ptr.getClass().hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0; - // We don't want to set OnPcEquip for items that need to be equipped; but cannot be equipped; - if (!ptr.getClass().getEquipmentSlots(ptr).first.empty() - && ptr.getClass().canBeEquipped(ptr, player).first == 0) - canUse = force && ptr.getClass().hasItemHealth(ptr) && ptr.getCellRef().getCharge() != 0; + // In vanilla, broken armor or weapons cannot be equipped + // tools with 0 charges is equippable + if (isBroken && isWeaponOrArmor) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}"); + return; + } + + bool canEquip = ptr.getClass().canBeEquipped(ptr, mPtr).first != 0; + bool shouldSetOnPcEquip = canEquip || force; // If the item has a script, set OnPCEquip or PCSkipEquip to 1 - if (!script.empty() && canUse) + if (!script.empty() && shouldSetOnPcEquip) { // Ingredients, books and repair hammers must not have OnPCEquip set to 1 here - auto type = ptr.getType(); bool isBook = type == ESM::Book::sRecordId; if (!isBook && type != ESM::Ingredient::sRecordId && type != ESM::Repair::sRecordId) ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1); @@ -543,25 +550,7 @@ namespace MWGui } std::unique_ptr action = ptr.getClass().use(ptr, force); - - action->execute(player, !canUse); - - if (mDragAndDrop->mIsOnDragAndDrop && mDragAndDrop->mItem.mBase == ptr) - { - if (canUse) - { - mDragAndDrop->finish(); - // If item is ingredient or potion don't stop drag and drop - if ((ptr.getType() == ESM::Potion::sRecordId || ptr.getType() == ESM::Ingredient::sRecordId) - && mDragAndDrop->mDraggedCount > 1) - { - mSelectedItem = getModel()->getIndex(mDragAndDrop->mItem); - dragItem(nullptr, mDragAndDrop->mDraggedCount - 1); - } - } - else - mDragAndDrop->drop(mTradeModel, mItemView); - } + action->execute(player); // Handles partial equipping (final part) if (mEquippedStackableCount.has_value()) @@ -592,6 +581,16 @@ namespace MWGui { MWWorld::Ptr ptr = mDragAndDrop->mItem.mBase; + auto canEquip = ptr.getClass().canBeEquipped(ptr, mPtr); + if (canEquip.first == 0) // cannot equip + { + mDragAndDrop->drop(mTradeModel, mItemView); // also plays down sound + MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second); + return; + } + + mDragAndDrop->finish(); + if (mDragAndDrop->mSourceModel != mTradeModel) { // Move item to the player's inventory @@ -615,6 +614,17 @@ namespace MWGui } MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer(), false); + + // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 + // item + if ((ptr.getType() == ESM::Potion::sRecordId || ptr.getType() == ESM::Ingredient::sRecordId) + && mDragAndDrop->mDraggedCount > 1) + { + // Item can be provided from other window for example container. + // But after DragAndDrop::startDrag item automaticly always gets to player inventory. + mSelectedItem = getModel()->getIndex(mDragAndDrop->mItem); + dragItem(nullptr, mDragAndDrop->mDraggedCount - 1); + } } else { diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index deb844078b..5f3cd17732 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -395,11 +395,10 @@ namespace MWGui if (!equipmentSlots.empty()) { const auto& itSlot = store.getSlot(equipmentSlots.front()); - // Morrowind.exe behaviour: - // Only display item broken message if; - // no item in the to-be-equipped slot - // or broken quickkey item and currently equipped item id is different - // It doesn't find a replacement + // Morrowind.exe behavior: + // Only display the "item is broken" message if: + // - There is no item in the target equipment slot, or + // - The quickkey item is broken and the currently equipped item has a different ID if (itSlot == store.end() || (item.getCellRef().getRefId() != itSlot->getCellRef().getRefId())) MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}"); } diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 58e6f013aa..dbc548f585 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -21,12 +21,11 @@ namespace MWWorld MWWorld::Ptr object = getTarget(); MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor); - if (object.getClass().hasItemHealth(object) && object.getCellRef().getCharge() == 0) + if (actor != MWMechanics::getPlayer()) { - if (actor == MWMechanics::getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}"); - - return; + // player logic is handled in InventoryWindow::useItem + if (object.getClass().hasItemHealth(object) && object.getCellRef().getCharge() == 0) + return; } if (!mForce) From ede768532c04d81f4e6e74019820b32cb6bc5657 Mon Sep 17 00:00:00 2001 From: Kuyondo Date: Tue, 8 Jul 2025 02:58:44 +0800 Subject: [PATCH 14/26] cleanup 2 --- apps/openmw/mwgui/inventorywindow.cpp | 8 ++++---- apps/openmw/mwgui/quickkeysmenu.cpp | 19 +------------------ apps/openmw/mwworld/inventorystore.cpp | 10 ++++++++++ apps/openmw/mwworld/inventorystore.hpp | 1 + 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 1f6b6fdeb5..acc1dd6068 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -550,7 +550,7 @@ namespace MWGui } std::unique_ptr action = ptr.getClass().use(ptr, force); - action->execute(player); + action->execute(player, !canEquip); // Handles partial equipping (final part) if (mEquippedStackableCount.has_value()) @@ -581,11 +581,11 @@ namespace MWGui { MWWorld::Ptr ptr = mDragAndDrop->mItem.mBase; - auto canEquip = ptr.getClass().canBeEquipped(ptr, mPtr); - if (canEquip.first == 0) // cannot equip + auto [canEquipRes, canEquipMsg] = ptr.getClass().canBeEquipped(ptr, mPtr); + if (canEquipRes == 0) // cannot equip { mDragAndDrop->drop(mTradeModel, mItemView); // also plays down sound - MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second); + MWBase::Environment::get().getWindowManager()->messageBox(canEquipMsg); return; } diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 5f3cd17732..191a2d8b3a 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -388,26 +388,9 @@ namespace MWGui return; } - // check the quickkey item is not broken - if (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0) - { - const std::vector& equipmentSlots = item.getClass().getEquipmentSlots(item).first; - if (!equipmentSlots.empty()) - { - const auto& itSlot = store.getSlot(equipmentSlots.front()); - // Morrowind.exe behavior: - // Only display the "item is broken" message if: - // - There is no item in the target equipment slot, or - // - The quickkey item is broken and the currently equipped item has a different ID - if (itSlot == store.end() || (item.getCellRef().getRefId() != itSlot->getCellRef().getRefId())) - MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}"); - } - return; - } - if (key->type == ESM::QuickKeys::Type::Item) { - if (!store.isEquipped(item)) + if (!store.isEquipped(item.getCellRef().getRefId())) MWBase::Environment::get().getWindowManager()->useItem(item); MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index f48f4e6e31..9143777531 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -754,6 +754,16 @@ bool MWWorld::InventoryStore::isEquipped(const MWWorld::ConstPtr& item) return false; } +bool MWWorld::InventoryStore::isEquipped(const ESM::RefId& id) +{ + for (int i = 0; i < MWWorld::InventoryStore::Slots; ++i) + { + if (getSlot(i) != end() && getSlot(i)->getCellRef().getRefId() == id) + return true; + } + return false; +} + bool MWWorld::InventoryStore::isFirstEquip() { bool first = mFirstAutoEquip; diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 0af6ee2b28..d0839dd1ed 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -118,6 +118,7 @@ namespace MWWorld ///< \warning \a iterator can not be an end()-iterator, use unequip function instead bool isEquipped(const MWWorld::ConstPtr& item); + bool isEquipped(const ESM::RefId& id); ///< Utility function, returns true if the given item is equipped in any slot void setSelectedEnchantItem(const ContainerStoreIterator& iterator); From 8b44b95830a5203f2b1ab0f8a441baabe6df86d3 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Tue, 8 Jul 2025 11:26:16 -0700 Subject: [PATCH 15/26] docs - fix table padding and luadoc scroll padding --- docs/source/_static/luadoc.css | 4 ++++ docs/source/_static/theme-override.css | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/source/_static/luadoc.css b/docs/source/_static/luadoc.css index b2efec85b4..96b7de9bb3 100644 --- a/docs/source/_static/luadoc.css +++ b/docs/source/_static/luadoc.css @@ -20,6 +20,10 @@ white-space: normal; } +html:has(#luadoc) { + scroll-padding-top: 4.0rem; +} + #content #luadoc code a:not(.toc-backref) { font-weight: 600; } diff --git a/docs/source/_static/theme-override.css b/docs/source/_static/theme-override.css index cf9ae168bc..756d920368 100644 --- a/docs/source/_static/theme-override.css +++ b/docs/source/_static/theme-override.css @@ -143,6 +143,10 @@ tbody tr:hover { margin-bottom: 1.5rem; } +#content table p { + margin-bottom: 0; +} + h5 { font-size: 1.15rem; font-weight: 600; From a68107712c0206a7c80c01e54b0bd4b1d5726b86 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 8 Jul 2025 22:33:35 +0400 Subject: [PATCH 16/26] Do not allow to select a screen resolution in the Windowed Fullscreen mode in the launcher --- apps/launcher/graphicspage.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 735bcf1df1..1e3caf7293 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -240,13 +240,26 @@ void Launcher::GraphicsPage::slotFullScreenChanged(int mode) void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode) { - if (mode == Settings::WindowMode::Fullscreen || mode == Settings::WindowMode::WindowedFullscreen) + if (mode == Settings::WindowMode::Fullscreen) { standardRadioButton->toggle(); customRadioButton->setEnabled(false); customWidthSpinBox->setEnabled(false); customHeightSpinBox->setEnabled(false); windowBorderCheckBox->setEnabled(false); + resolutionComboBox->setEnabled(true); + } + else if (mode == Settings::WindowMode::WindowedFullscreen) + { + standardRadioButton->toggle(); + customRadioButton->setEnabled(false); + customWidthSpinBox->setEnabled(false); + customHeightSpinBox->setEnabled(false); + windowBorderCheckBox->setEnabled(false); + resolutionComboBox->setEnabled(false); + + // Assume that a first item is a native screen resolution + resolutionComboBox->setCurrentIndex(0); } else { @@ -254,6 +267,7 @@ void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode) customWidthSpinBox->setEnabled(true); customHeightSpinBox->setEnabled(true); windowBorderCheckBox->setEnabled(true); + resolutionComboBox->setEnabled(true); } } From 3ec04f4ba754790040b634348f41ce3966170581 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Tue, 8 Jul 2025 14:15:37 -0700 Subject: [PATCH 17/26] docs - improve link contrast in dark mode --- docs/source/_static/theme-override.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/_static/theme-override.css b/docs/source/_static/theme-override.css index 756d920368..25f7bd6f10 100644 --- a/docs/source/_static/theme-override.css +++ b/docs/source/_static/theme-override.css @@ -36,8 +36,8 @@ --muted: 223 27% 14%; --card: 220 14% 9%; - --link: #ffffff; - --link-hover: #ffffff; + --link: #95b1dd; + --link-hover: #dde2eb; } .contents ul li a.reference:hover, .sd-dropdown .toctree-wrapper ul li a.reference, details.sd-dropdown .sd-summary-title { From 759739eccaae3dbe716673c06e1f0aaf2d1a536f Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Tue, 8 Jul 2025 15:53:18 -0700 Subject: [PATCH 18/26] generate mipmaps after we draw, not before --- apps/openmw/mwrender/pingpongcanvas.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 54d8145fa9..303f2a9bae 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -280,15 +280,6 @@ namespace MWRender { pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); - if (pass.mRenderTexture->getNumMipmapLevels() > 0) - { - state.setActiveTextureUnit(0); - state.applyTextureAttribute(0, - pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0) - .getTexture()); - ext->glGenerateMipmap(GL_TEXTURE_2D); - } - lastApplied = pass.mRenderTarget->getHandle(state.getContextID()); } else if (pass.mResolve && index == filtered.back()) @@ -325,6 +316,15 @@ namespace MWRender drawGeometry(renderInfo); + if (pass.mRenderTexture && pass.mRenderTexture->getNumMipmapLevels() > 0) + { + state.setActiveTextureUnit(0); + state.applyTextureAttribute(0, + pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0) + .getTexture()); + ext->glGenerateMipmap(GL_TEXTURE_2D); + } + state.popStateSet(); state.apply(); } From 064b8d3f9bc51d4623bde8f3ade78274918336a7 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Wed, 9 Jul 2025 11:49:38 -0700 Subject: [PATCH 19/26] use more explicit null check --- apps/openmw/mwrender/pingpongcanvas.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 303f2a9bae..5a37e09d84 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -316,7 +316,7 @@ namespace MWRender drawGeometry(renderInfo); - if (pass.mRenderTexture && pass.mRenderTexture->getNumMipmapLevels() > 0) + if (pass.mRenderTarget && pass.mRenderTexture->getNumMipmapLevels() > 0) { state.setActiveTextureUnit(0); state.applyTextureAttribute(0, From dca83c2500276aed19d6a47e4eb26d4b65299782 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 9 Jul 2025 18:30:08 +0400 Subject: [PATCH 20/26] Add resolution hints to launcher --- apps/launcher/graphicspage.cpp | 30 ++++++++++++++++++++++++------ files/data/l10n/OMWEngine/ru.yaml | 2 +- files/lang/launcher_de.ts | 12 ++++++++++++ files/lang/launcher_en.ts | 12 ++++++++++++ files/lang/launcher_fr.ts | 12 ++++++++++++ files/lang/launcher_ru.ts | 12 ++++++++++++ files/lang/launcher_sv.ts | 12 ++++++++++++ 7 files changed, 85 insertions(+), 7 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 1e3caf7293..38b0446efc 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -240,23 +240,35 @@ void Launcher::GraphicsPage::slotFullScreenChanged(int mode) void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode) { - if (mode == Settings::WindowMode::Fullscreen) + if (mode == Settings::WindowMode::Fullscreen || mode == Settings::WindowMode::WindowedFullscreen) { + QString customSizeMessage = tr("Custom window size is available only in Windowed mode."); + QString windowBorderMessage = tr("Window border is available only in Windowed mode."); + standardRadioButton->toggle(); customRadioButton->setEnabled(false); customWidthSpinBox->setEnabled(false); customHeightSpinBox->setEnabled(false); windowBorderCheckBox->setEnabled(false); + windowBorderCheckBox->setToolTip(windowBorderMessage); + customWidthSpinBox->setToolTip(customSizeMessage); + customHeightSpinBox->setToolTip(customSizeMessage); + customRadioButton->setToolTip(customSizeMessage); + } + + if (mode == Settings::WindowMode::Fullscreen) + { resolutionComboBox->setEnabled(true); + resolutionComboBox->setToolTip(""); + standardRadioButton->setToolTip(""); } else if (mode == Settings::WindowMode::WindowedFullscreen) { - standardRadioButton->toggle(); - customRadioButton->setEnabled(false); - customWidthSpinBox->setEnabled(false); - customHeightSpinBox->setEnabled(false); - windowBorderCheckBox->setEnabled(false); + QString fullScreenMessage = tr("Windowed Fullscreen mode always uses the native display resolution."); + resolutionComboBox->setEnabled(false); + resolutionComboBox->setToolTip(fullScreenMessage); + standardRadioButton->setToolTip(fullScreenMessage); // Assume that a first item is a native screen resolution resolutionComboBox->setCurrentIndex(0); @@ -268,6 +280,12 @@ void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode) customHeightSpinBox->setEnabled(true); windowBorderCheckBox->setEnabled(true); resolutionComboBox->setEnabled(true); + resolutionComboBox->setToolTip(""); + standardRadioButton->setToolTip(""); + windowBorderCheckBox->setToolTip(""); + customWidthSpinBox->setToolTip(""); + customHeightSpinBox->setToolTip(""); + customRadioButton->setToolTip(""); } } diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index c50fbac38e..753d1fa219 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -208,7 +208,7 @@ WaterShaderTextureQuality: "Качество текстуры воды" WindowBorder: "Рамка окна" WindowMode: "Режим окна" WindowModeFullscreen: "Полный экран" -WindowModeHint: "Подсказка: режим Оконный без полей\nвсегда использует родное разрешение экрана." +WindowModeHint: "Подсказка: режим \"Оконный без полей\"\nвсегда использует родное разрешение экрана." WindowModeWindowed: "Оконный" WindowModeWindowedFullscreen: "Оконный без полей" WobblyShores: "Колеблющиеся берега" diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 86773e5a54..94437fb822 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -425,6 +425,18 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <br><b>SDL_GetDisplayMode failed:</b><br><br> + + Custom window size is available only in Windowed mode. + + + + Window border is available only in Windowed mode. + + + + Windowed Fullscreen mode always uses the native display resolution. + + Launcher::ImportPage diff --git a/files/lang/launcher_en.ts b/files/lang/launcher_en.ts index a0319318e8..25717d1454 100644 --- a/files/lang/launcher_en.ts +++ b/files/lang/launcher_en.ts @@ -425,6 +425,18 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <br><b>SDL_GetDisplayMode failed:</b><br><br> + + Custom window size is available only in Windowed mode. + + + + Window border is available only in Windowed mode. + + + + Windowed Fullscreen mode always uses the native display resolution. + + Launcher::ImportPage diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 569a460cd0..5f8f020981 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -425,6 +425,18 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <br><b>SDL_GetDisplayMode failed:</b><br><br> <br><b>SDL_GetDisplayMode failed:</b><br><br> + + Custom window size is available only in Windowed mode. + La taille personalisée de fenêtre n'est disponible qu'en mode fenêtré. + + + Window border is available only in Windowed mode. + Les bordures de fenêtres ne sont disponibles qu'en mode fenêtré. + + + Windowed Fullscreen mode always uses the native display resolution. + Le mode "Fenêtré plein écran" utilise toujours la résolution native de l'écran. + Launcher::ImportPage diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 879f24dc76..bf0ea467b2 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -427,6 +427,18 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <br><b>SDL_GetDisplayMode failed:</b><br><br> <br><b>Вызов SDL_GetDisplayMode завершился с ошибкой:</b><br><br> + + Custom window size is available only in Windowed mode. + Особый размер окна доступен только в оконном режиме. + + + Window border is available only in Windowed mode. + Рамка окна доступна только в оконном режиме. + + + Windowed Fullscreen mode always uses the native display resolution. + Режим "Оконный без полей" всегда использует родное разрешение экрана. + Launcher::ImportPage diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts index f2cca2346c..97de9603d7 100644 --- a/files/lang/launcher_sv.ts +++ b/files/lang/launcher_sv.ts @@ -428,6 +428,18 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin <br><b>SDL_GetDisplayMode failed:</b><br><br> <br><b>SDL_GetDisplayMode misslyckades:</b><br><br> + + Custom window size is available only in Windowed mode. + Anpassad fönsterstorlek finns endast tillgänglig i fönsterläge. + + + Window border is available only in Windowed mode. + Fönsterram finns endast tillgänglig i fönsterläge + + + Windowed Fullscreen mode always uses the native display resolution. + Helskärm i fönsterläge använder alltid skärmens nativa upplösning. + Launcher::ImportPage From 7bc507eb587827b1fa75a6d842b08ed4df06b676 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 31 Jan 2025 18:00:05 -0700 Subject: [PATCH 21/26] FIX: Skip only the current subrecord when reading quest status --- components/esm3/loadinfo.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/esm3/loadinfo.cpp b/components/esm3/loadinfo.cpp index 8b1147ed45..89ba6d8aff 100644 --- a/components/esm3/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -76,15 +76,15 @@ namespace ESM break; case fourCC("QSTN"): mQuestStatus = QS_Name; - esm.skipRecord(); + esm.skipHSub(); break; case fourCC("QSTF"): mQuestStatus = QS_Finished; - esm.skipRecord(); + esm.skipHSub(); break; case fourCC("QSTR"): mQuestStatus = QS_Restart; - esm.skipRecord(); + esm.skipHSub(); break; case SREC_DELE: esm.skipHSub(); From 25d9c80067a0d91b5820fc131da70ae8bb4d901b Mon Sep 17 00:00:00 2001 From: Kuyondo Date: Thu, 10 Jul 2025 23:46:22 +0800 Subject: [PATCH 22/26] Don't play vfx and sound on fully resisted debuffs --- apps/openmw/mwmechanics/activespells.cpp | 8 +++----- apps/openmw/mwmechanics/spelleffects.cpp | 11 +++++++---- apps/openmw/mwmechanics/spelleffects.hpp | 3 ++- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 0f361f7ffc..270faf8598 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -273,13 +273,14 @@ namespace MWMechanics } bool updateSpellWindow = false; + bool playNonLooping = false; if (ptr.getClass().hasInventoryStore(ptr) && !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished())) { auto& store = ptr.getClass().getInventoryStore(ptr); if (store.getInvListener() != nullptr) { - bool playNonLooping = !store.isFirstEquip(); + playNonLooping = !store.isFirstEquip(); const auto world = MWBase::Environment::get().getWorld(); for (int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++) { @@ -307,9 +308,6 @@ namespace MWMechanics applyPurges(ptr); ActiveSpellParams& params = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr }); params.setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); - for (const auto& effect : params.mEffects) - MWMechanics::playEffects( - ptr, *world->getStore().get().find(effect.mEffectId), playNonLooping); updateSpellWindow = true; } } @@ -327,7 +325,7 @@ namespace MWMechanics std::optional reflected; for (auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();) { - auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration); + auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration, playNonLooping); if (result.mType == MagicApplicationResult::Type::REFLECTED) { if (!reflected) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 3d1a5ed84e..99e5a09481 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -914,7 +914,7 @@ namespace MWMechanics } MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, - ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt) + ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt, bool playNonLooping) { const auto world = MWBase::Environment::get().getWorld(); bool invalid = false; @@ -1032,9 +1032,12 @@ namespace MWMechanics oldMagnitude = effect.mMagnitude; else { - if (!spellParams.hasFlag(ESM::ActiveSpells::Flag_Equipment) - && !spellParams.hasFlag(ESM::ActiveSpells::Flag_Lua)) - playEffects(target, *magicEffect, spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary)); + bool isTemporary = spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary); + bool isEquipment = spellParams.hasFlag(ESM::ActiveSpells::Flag_Equipment); + + if (!spellParams.hasFlag(ESM::ActiveSpells::Flag_Lua)) + playEffects(target, *magicEffect, (isTemporary || (isEquipment && playNonLooping))); + if (effect.mEffectId == ESM::MagicEffect::Soultrap && !target.getClass().isNpc() && target.getType() == ESM::Creature::sRecordId && target.get()->mBase->mData.mSoul == 0 && caster == getPlayer()) diff --git a/apps/openmw/mwmechanics/spelleffects.hpp b/apps/openmw/mwmechanics/spelleffects.hpp index 2dafedf31f..d9b05535a9 100644 --- a/apps/openmw/mwmechanics/spelleffects.hpp +++ b/apps/openmw/mwmechanics/spelleffects.hpp @@ -28,7 +28,8 @@ namespace MWMechanics // Applies a tick of a single effect. Returns true if the effect should be removed immediately MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, - ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt); + ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt, + bool playNonLoopingEffect = true); // Undoes permanent effects created by ESM::MagicEffect::AppliedOnce void onMagicEffectRemoved( From 869881d227bbd412dd85bf3cc84ae96e4f86f59c Mon Sep 17 00:00:00 2001 From: wareya Date: Fri, 11 Jul 2025 12:23:22 -0400 Subject: [PATCH 23/26] fix terrain normal map handedness --- files/shaders/compatibility/terrain.frag | 2 +- files/shaders/compatibility/terrain.vert | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index 9d89217f35..48183c0a92 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -49,7 +49,7 @@ void main() vec2 adjustedUV = (gl_TextureMatrix[0] * vec4(uv, 0.0, 1.0)).xy; #if @parallax - adjustedUV += getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), texture2D(normalMap, adjustedUV).a, 1.f); + adjustedUV += getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), texture2D(normalMap, adjustedUV).a, -1.0f); #endif vec4 diffuseTex = texture2D(diffuseMap, adjustedUV); gl_FragData[0] = vec4(diffuseTex.xyz, 1.0); diff --git a/files/shaders/compatibility/terrain.vert b/files/shaders/compatibility/terrain.vert index cbfb7769ba..40d69ff35d 100644 --- a/files/shaders/compatibility/terrain.vert +++ b/files/shaders/compatibility/terrain.vert @@ -46,8 +46,8 @@ void main(void) normalToViewMatrix = gl_NormalMatrix; #if @normalMap - mat3 tbnMatrix = generateTangentSpace(vec4(1.0, 0.0, 0.0, 1.0), passNormal); - tbnMatrix[0] = normalize(cross(tbnMatrix[2], tbnMatrix[1])); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + mat3 tbnMatrix = generateTangentSpace(vec4(1.0, 0.0, 0.0, -1.0), passNormal); + tbnMatrix[0] = -normalize(cross(tbnMatrix[2], tbnMatrix[1])); // our original tangent was not at a 90 degree angle to the normal, so we need to rederive it normalToViewMatrix *= tbnMatrix; #endif From 77c30a68c99871135a36190be1bc5383235ae3f0 Mon Sep 17 00:00:00 2001 From: Kuyondo Date: Sat, 12 Jul 2025 11:56:31 +0800 Subject: [PATCH 24/26] more similar to vanilla --- apps/openmw/mwgui/jailscreen.cpp | 2 +- apps/openmw/mwgui/timeadvancer.cpp | 5 ++--- apps/openmw/mwgui/timeadvancer.hpp | 4 ++-- apps/openmw/mwgui/trainingwindow.cpp | 2 +- apps/openmw/mwgui/waitdialog.cpp | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index c6aefdd177..f482ec4de2 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -23,7 +23,7 @@ namespace MWGui : WindowBase("openmw_jail_screen.layout") , mDays(1) , mFadeTimeRemaining(0) - , mTimeAdvancer(0.01f) + , mTimeAdvancer() { getWidget(mProgressBar, "ProgressBar"); diff --git a/apps/openmw/mwgui/timeadvancer.cpp b/apps/openmw/mwgui/timeadvancer.cpp index 2cdab127b9..abe0a668d4 100644 --- a/apps/openmw/mwgui/timeadvancer.cpp +++ b/apps/openmw/mwgui/timeadvancer.cpp @@ -2,13 +2,12 @@ namespace MWGui { - TimeAdvancer::TimeAdvancer(float delay) + TimeAdvancer::TimeAdvancer() : mRunning(false) , mCurHour(0) , mHours(1) , mInterruptAt(-1) - , mDelay(delay) - , mRemainingTime(delay) + , mRemainingTime(mDelay) { } diff --git a/apps/openmw/mwgui/timeadvancer.hpp b/apps/openmw/mwgui/timeadvancer.hpp index bb6aa649cb..be19619d25 100644 --- a/apps/openmw/mwgui/timeadvancer.hpp +++ b/apps/openmw/mwgui/timeadvancer.hpp @@ -8,7 +8,7 @@ namespace MWGui class TimeAdvancer { public: - TimeAdvancer(float delay); + TimeAdvancer(); void run(int hours, int interruptAt = -1); void stop(); @@ -32,7 +32,7 @@ namespace MWGui int mHours; int mInterruptAt; - float mDelay; + static constexpr float mDelay = 1.0f / 60.0f; float mRemainingTime; }; } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 890aa0ba68..94f001f26d 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -27,7 +27,7 @@ namespace MWGui TrainingWindow::TrainingWindow() : WindowBase("openmw_trainingwindow.layout") - , mTimeAdvancer(0.05f) + , mTimeAdvancer() { getWidget(mTrainingOptions, "TrainingOptions"); getWidget(mCancelButton, "CancelButton"); diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 222a34e53b..f6f1b86214 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -52,7 +52,7 @@ namespace MWGui WaitDialog::WaitDialog() : WindowBase("openmw_wait_dialog.layout") - , mTimeAdvancer(0.05f) + , mTimeAdvancer() , mSleeping(false) , mHours(1) , mManualHours(1) From d2c78ee88cc89b41206c7703fed64eef3cdb2bbc Mon Sep 17 00:00:00 2001 From: Kuyondo Date: Sat, 12 Jul 2025 22:14:47 +0800 Subject: [PATCH 25/26] move member var to implementation --- apps/openmw/mwgui/timeadvancer.cpp | 12 +++++++++--- apps/openmw/mwgui/timeadvancer.hpp | 1 - 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/timeadvancer.cpp b/apps/openmw/mwgui/timeadvancer.cpp index abe0a668d4..c4bdc030c2 100644 --- a/apps/openmw/mwgui/timeadvancer.cpp +++ b/apps/openmw/mwgui/timeadvancer.cpp @@ -1,5 +1,11 @@ #include "timeadvancer.hpp" +namespace +{ + // Time per hour tick + constexpr float kProgressStepDelay = 1.0f / 60.0f; +} + namespace MWGui { TimeAdvancer::TimeAdvancer() @@ -7,7 +13,7 @@ namespace MWGui , mCurHour(0) , mHours(1) , mInterruptAt(-1) - , mRemainingTime(mDelay) + , mRemainingTime(kProgressStepDelay) { } @@ -16,7 +22,7 @@ namespace MWGui mHours = hours; mCurHour = 0; mInterruptAt = interruptAt; - mRemainingTime = mDelay; + mRemainingTime = kProgressStepDelay; mRunning = true; } @@ -42,7 +48,7 @@ namespace MWGui while (mRemainingTime <= 0) { - mRemainingTime += mDelay; + mRemainingTime += kProgressStepDelay; ++mCurHour; if (mCurHour <= mHours) diff --git a/apps/openmw/mwgui/timeadvancer.hpp b/apps/openmw/mwgui/timeadvancer.hpp index be19619d25..e69153aed4 100644 --- a/apps/openmw/mwgui/timeadvancer.hpp +++ b/apps/openmw/mwgui/timeadvancer.hpp @@ -32,7 +32,6 @@ namespace MWGui int mHours; int mInterruptAt; - static constexpr float mDelay = 1.0f / 60.0f; float mRemainingTime; }; } From def31cfb05a29fb65e199d45cba97c54358b5ae6 Mon Sep 17 00:00:00 2001 From: Kuyondo Date: Sat, 12 Jul 2025 22:44:28 +0800 Subject: [PATCH 26/26] cleanup --- apps/openmw/mwgui/jailscreen.cpp | 1 - apps/openmw/mwgui/trainingwindow.cpp | 1 - apps/openmw/mwgui/waitdialog.cpp | 1 - 3 files changed, 3 deletions(-) diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index f482ec4de2..2fbaa8d8ac 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -23,7 +23,6 @@ namespace MWGui : WindowBase("openmw_jail_screen.layout") , mDays(1) , mFadeTimeRemaining(0) - , mTimeAdvancer() { getWidget(mProgressBar, "ProgressBar"); diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 94f001f26d..4bde77a552 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -27,7 +27,6 @@ namespace MWGui TrainingWindow::TrainingWindow() : WindowBase("openmw_trainingwindow.layout") - , mTimeAdvancer() { getWidget(mTrainingOptions, "TrainingOptions"); getWidget(mCancelButton, "CancelButton"); diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index f6f1b86214..9609def96d 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -52,7 +52,6 @@ namespace MWGui WaitDialog::WaitDialog() : WindowBase("openmw_wait_dialog.layout") - , mTimeAdvancer() , mSleeping(false) , mHours(1) , mManualHours(1)