diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 735bcf1df1..38b0446efc 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -242,11 +242,36 @@ void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode) { 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) + { + 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); } else { @@ -254,6 +279,13 @@ void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode) customWidthSpinBox->setEnabled(true); customHeightSpinBox->setEnabled(true); windowBorderCheckBox->setEnabled(true); + resolutionComboBox->setEnabled(true); + resolutionComboBox->setToolTip(""); + standardRadioButton->setToolTip(""); + windowBorderCheckBox->setToolTip(""); + customWidthSpinBox->setToolTip(""); + customHeightSpinBox->setToolTip(""); + customRadioButton->setToolTip(""); } } 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 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 642cfed05d..625755fdfc 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -614,36 +614,25 @@ namespace MWGui } MWWorld::Ptr player = MWMechanics::getPlayer(); + auto type = ptr.getType(); + bool isWeaponOrArmor = type == ESM::Weapon::sRecordId || type == ESM::Armor::sRecordId; + bool isBroken = ptr.getClass().hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0; - // 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()) + // In vanilla, broken armor or weapons cannot be equipped + // tools with 0 charges is equippable + if (isBroken && isWeaponOrArmor) { - 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; - } - } + 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()) + 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); @@ -653,7 +642,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()) @@ -684,6 +673,14 @@ namespace MWGui { MWWorld::Ptr ptr = mDragAndDrop->mItem.mBase; + 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(canEquipMsg); + return; + } + mDragAndDrop->finish(); if (mDragAndDrop->mSourceModel != mTradeModel) diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index c6aefdd177..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(0.01f) { getWidget(mProgressBar, "ProgressBar"); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 96045b8e21..1b25b8b7d2 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -90,9 +90,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); @@ -259,6 +258,8 @@ namespace MWGui if (mItemSelectionDialog) mItemSelectionDialog->setVisible(false); + + MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item)); } void QuickKeysMenu::onAssignItemCancel() @@ -395,23 +396,16 @@ 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); - - if (item.isEmpty() || item.getCellRef().getCount() < 1) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + key->name); - - return; - } + MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + key->name); + 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/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()) diff --git a/apps/openmw/mwgui/timeadvancer.cpp b/apps/openmw/mwgui/timeadvancer.cpp index 2cdab127b9..c4bdc030c2 100644 --- a/apps/openmw/mwgui/timeadvancer.cpp +++ b/apps/openmw/mwgui/timeadvancer.cpp @@ -1,14 +1,19 @@ #include "timeadvancer.hpp" +namespace +{ + // Time per hour tick + constexpr float kProgressStepDelay = 1.0f / 60.0f; +} + namespace MWGui { - TimeAdvancer::TimeAdvancer(float delay) + TimeAdvancer::TimeAdvancer() : mRunning(false) , mCurHour(0) , mHours(1) , mInterruptAt(-1) - , mDelay(delay) - , mRemainingTime(delay) + , mRemainingTime(kProgressStepDelay) { } @@ -17,7 +22,7 @@ namespace MWGui mHours = hours; mCurHour = 0; mInterruptAt = interruptAt; - mRemainingTime = mDelay; + mRemainingTime = kProgressStepDelay; mRunning = true; } @@ -43,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 bb6aa649cb..e69153aed4 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,6 @@ namespace MWGui int mHours; int mInterruptAt; - float mDelay; float mRemainingTime; }; } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 5b4d355e61..1a268fff5d 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(0.05f) { getWidget(mTrainingOptions, "TrainingOptions"); getWidget(mCancelButton, "CancelButton"); diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index f33695a982..fcd7b21181 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(0.05f) , mSleeping(false) , mHours(1) , mManualHours(1) 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/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 1e62cc4a21..a4c2f42f0f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -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; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 2399961a3a..1b0b0e5a89 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; @@ -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 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( diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 54d8145fa9..5a37e09d84 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.mRenderTarget && 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(); } 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"); } } 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) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 5f7e135847..f31e261106 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -732,6 +732,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 4e8f56f616..33de6f66d3 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -117,6 +117,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); 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(); 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..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 { @@ -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; diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index 6a4e265791..b780ba1e05 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/data/mygui/openmw_quickkeys_menu.layout b/files/data/mygui/openmw_quickkeys_menu.layout index ba6832cf95..697f186411 100644 --- a/files/data/mygui/openmw_quickkeys_menu.layout +++ b/files/data/mygui/openmw_quickkeys_menu.layout @@ -1,36 +1,35 @@ - + - + - + - - + - - - - - + + + + + - + diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index fe36e91646..d83a69f990 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 4097d29dd2..984dbafbc5 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 3d2363130e..52f22bc65e 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 7f0400670d..ec7d80281d 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 63912de9e1..66c86fd791 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 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