diff --git a/AUTHORS.md b/AUTHORS.md index 415b87b4e..f1e6e58ee 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -22,6 +22,7 @@ Programmers alexanderkjall Alexander Nadeau (wareya) Alexander Olofsson (Ace) + Alex S (docwest) Allofich Andrei Kortunov (akortunov) AnyOldName3 @@ -63,6 +64,7 @@ Programmers Evgeniy Mineev (sandstranger) Federico Guerra (FedeWar) Fil Krynicki (filkry) + Finbar Crago (finbar-crago) Florian Weber (Florianjw) Gašper Sedej gugus/gus diff --git a/CHANGELOG.md b/CHANGELOG.md index 39af121aa..f839279f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Bug #1990: Sunrise/sunset not set correct Bug #2222: Fatigue's effect on selling price is backwards Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped + Bug #2455: Creatures attacks degrade armor Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash Bug #2772: Non-existing class or faction freezes the game Bug #2835: Player able to slowly move when overencumbered @@ -48,14 +49,17 @@ Bug #4459: NotCell dialogue condition doesn't support partial matches Bug #4461: "Open" spell from non-player caster isn't a crime Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal - Bug #4471: Retrieve SDL window settings instead of using magic numbers Bug #4474: No fallback when getVampireHead fails Bug #4475: Scripted animations should not cause movement + Bug #4479: "Game" category on Advanced page is getting too long + Bug #4480: Segfalt in QuickKeysMenu when item no longer in inventory Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results + Feature #3641: Editor: Limit FPS in 3d preview window Feature #4222: 360° screenshots Feature #4256: Implement ToggleBorders (TB) console command Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts Feature #4345: Add equivalents for the command line commands to Launcher + Feature #4404: Editor: All EnumDelegate fields should have their items sorted alphabetically Feature #4444: Per-group KF-animation files support Feature #4466: Editor: Add option to ignore "Base" records when running verifier diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 56a9d92d9..264d33dd2 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -452,6 +452,7 @@ fi add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.${LIB_SUFFIX}" add_cmake_opts -DBoost_COMPILER="-${TOOLSET}" + echo Done. fi } diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 2b2d7b448..b0a35f0a5 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -73,18 +73,8 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); - loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); - loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); - loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); - loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); - // Expected values are (0, 1, 2, 3) - int showOwnedIndex = mEngineSettings.getInt("show owned", "Game"); - // Match the index with the option. Will default to 0 if invalid. - if (showOwnedIndex >= 0 && showOwnedIndex <= 3) - showOwnedComboBox->setCurrentIndex(showOwnedIndex); - // Input Settings loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); loadSettingBool(grabCursorCheckBox, "grab cursor", "Input"); @@ -94,6 +84,16 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves")); + // User Interface Settings + loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); + loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); + loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); + loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); + int showOwnedIndex = mEngineSettings.getInt("show owned", "Game"); + // Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid. + if (showOwnedIndex >= 0 && showOwnedIndex <= 3) + showOwnedComboBox->setCurrentIndex(showOwnedIndex); + // Other Settings QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper(); if (screenshotFormatComboBox->findText(screenshotFormatString) == -1) @@ -125,16 +125,8 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); - saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); - saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); - saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); - saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); - int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); - if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) - mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); - // Input Settings saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); saveSettingBool(grabCursorCheckBox, "grab cursor", "Input"); @@ -147,6 +139,15 @@ void Launcher::AdvancedPage::saveSettings() mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves); } + // User Interface Settings + saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); + saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); + saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); + saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); + int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); + if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) + mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); + // Other Settings std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General")) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index e1236a0e4..a704fb825 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -201,6 +201,9 @@ void CSMPrefs::State::declare() declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); declareCategory ("Rendering"); + declareInt ("framerate-limit", "FPS limit", 60). + setTooltip("Framerate limit in 3D preview windows. Zero value means \"unlimited\"."). + setRange(0, 10000); declareInt ("camera-fov", "Camera FOV", 90).setRange(10, 170); declareBool ("camera-ortho", "Orthographic projection for camera", false); declareInt ("camera-ortho-size", "Orthographic projection size parameter", 100). diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index f24a9de50..0d1780d57 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -151,6 +151,9 @@ CompositeViewer::CompositeViewer() connect( &mTimer, SIGNAL(timeout()), this, SLOT(update()) ); mTimer.start( 10 ); + + int frameRateLimit = CSMPrefs::get()["Rendering"]["framerate-limit"].toInt(); + setRunMaxFrameRate(frameRateLimit); } CompositeViewer &CompositeViewer::get() @@ -168,6 +171,12 @@ void CompositeViewer::update() mSimulationTime += dt; frame(mSimulationTime); + + double minFrameTime = _runMaxFrameRate > 0.0 ? 1.0 / _runMaxFrameRate : 0.0; + if (dt < minFrameTime) + { + OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-dt)); + } } // --------------------------------------------------- @@ -376,6 +385,10 @@ void SceneWidget::settingChanged (const CSMPrefs::Setting *setting) { mOrbitCamControl->setConstRoll(setting->isTrue()); } + else if (*setting=="Rendering/framerate-limit") + { + CompositeViewer::get().setRunMaxFrameRate(setting->toInt()); + } else if (*setting=="Rendering/camera-fov" || *setting=="Rendering/camera-ortho" || *setting=="Rendering/camera-ortho-size") diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index e582e3356..c303b71ac 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -110,7 +110,11 @@ void CSVWorld::EnumDelegate::paint (QPainter *painter, const QStyleOptionViewIte int valueIndex = getValueIndex(index); if (valueIndex != -1) { +#if QT_VERSION >= QT_VERSION_CHECK(5,7,0) + QStyleOptionViewItem itemOption(option); +#else QStyleOptionViewItemV4 itemOption(option); +#endif itemOption.text = mValues.at(valueIndex).second; QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &itemOption, painter); } @@ -171,5 +175,16 @@ CSVWorld::CommandDelegate *CSVWorld::EnumDelegateFactory::makeDelegate ( void CSVWorld::EnumDelegateFactory::add (int value, const QString& name) { - mValues.push_back (std::make_pair (value, name)); + auto pair = std::make_pair (value, name); + + for (auto it=mValues.begin(); it!=mValues.end(); ++it) + { + if (it->second > name) + { + mValues.insert(it, pair); + return; + } + } + + mValues.push_back(std::make_pair (value, name)); } diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 8c3c9494c..93153da87 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -381,33 +381,24 @@ void OMW::Engine::createWindow(Settings::Manager& settings) setWindowIcon(); osg::ref_ptr traits = new osg::GraphicsContext::Traits; - int redSize; - int greenSize; - int blueSize; - int depthSize; - int stencilSize; - int doubleBuffer; - - SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &redSize); - SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &greenSize); - SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &blueSize); - SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depthSize); - SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &stencilSize); - SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &doubleBuffer); - SDL_GetWindowPosition(mWindow, &traits->x, &traits->y); SDL_GetWindowSize(mWindow, &traits->width, &traits->height); - traits->red = redSize; - traits->green = greenSize; - traits->blue = blueSize; - traits->depth = depthSize; - traits->stencil = stencilSize; - traits->doubleBuffer = doubleBuffer; traits->windowName = SDL_GetWindowTitle(mWindow); traits->windowDecoration = !(SDL_GetWindowFlags(mWindow)&SDL_WINDOW_BORDERLESS); traits->screenNum = SDL_GetWindowDisplayIndex(mWindow); - traits->vsync = vsync; + // We tried to get rid of the hardcoding but failed: https://github.com/OpenMW/openmw/pull/1771 + // Here goes kcat's quote: + // It's ultimately a chicken and egg problem, and the reason why the code is like it was in the first place. + // It needs a context to get the current attributes, but it needs the attributes to set up the context. + // So it just specifies the same values that were given to SDL in the hopes that it's good enough to what the window eventually gets. + traits->red = 8; + traits->green = 8; + traits->blue = 8; traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel + traits->depth = 24; + traits->stencil = 8; + traits->vsync = vsync; + traits->doubleBuffer = true; traits->inheritedWindowData = new SDLUtil::GraphicsWindowSDL2::WindowData(mWindow); osg::ref_ptr graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 92e25baee..0becb18df 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -773,22 +773,24 @@ namespace MWClass float x = damage / (damage + getArmorRating(ptr)); damage *= std::max(gmst.fCombatArmorMinMult->getFloat(), x); int damageDiff = static_cast(unmitigatedDamage - damage); - if (damage < 1) - damage = 1; + damage = std::max(1.f, damage); + damageDiff = std::max(1, damageDiff); MWWorld::InventoryStore &inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot); MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr()); if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name()) { - int armorhealth = armor.getClass().getItemHealth(armor); - armorhealth -= std::min(std::max(1, damageDiff), - armorhealth); - armor.getCellRef().setCharge(armorhealth); - - // Armor broken? unequip it - if (armorhealth == 0) - armor = *inv.unequipItem(armor, ptr); + if (attacker.isEmpty() || (!attacker.isEmpty() && !(object.isEmpty() && !attacker.getClass().isNpc()))) // Unarmed creature attacks don't affect armor condition + { + int armorhealth = armor.getClass().getItemHealth(armor); + armorhealth -= std::min(damageDiff, armorhealth); + armor.getCellRef().setCharge(armorhealth); + + // Armor broken? unequip it + if (armorhealth == 0) + armor = *inv.unequipItem(armor, ptr); + } if (ptr == MWMechanics::getPlayer()) skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0); diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index e0bcbe667..156a17e67 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -81,7 +81,7 @@ namespace MWGui MyGUI::Widget* getDefaultKeyFocus() override; - virtual bool exit() { return false; } + virtual bool exit() override { return false; } bool mMarkedToDelete; diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 08192625f..badf50213 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -33,44 +33,41 @@ namespace MWGui QuickKeysMenu::QuickKeysMenu() : WindowBase("openmw_quickkeys_menu.layout") + , mKey(std::vector(10)) + , mSelected(nullptr) + , mActivated(nullptr) , mAssignDialog(0) , mItemSelectionDialog(0) , mMagicSelectionDialog(0) - , mSelectedIndex(-1) - , mActivatedIndex(-1) + { getWidget(mOkButton, "OKButton"); getWidget(mInstructionLabel, "InstructionLabel"); mMainWidget->setSize(mMainWidget->getWidth(), - mMainWidget->getHeight() + (mInstructionLabel->getTextSize().height - mInstructionLabel->getHeight())); + mMainWidget->getHeight() + + (mInstructionLabel->getTextSize().height - mInstructionLabel->getHeight())); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onOkButtonClicked); center(); - for (int i = 0; i < 10; ++i) { - ItemWidget* button; - getWidget(button, "QuickKey" + MyGUI::utility::toString(i+1)); - - button->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); + mKey[i].index = i+1; + getWidget(mKey[i].button, "QuickKey" + MyGUI::utility::toString(i+1)); + mKey[i].button->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); - mQuickKeyButtons.push_back(button); - - mAssigned.push_back(Type_Unassigned); - - unassign(button, i); + unassign(&mKey[i]); } } void QuickKeysMenu::clear() { - mActivatedIndex = -1; + mActivated = nullptr; for (int i=0; i<10; ++i) { - unassign(mQuickKeyButtons[i], i); + unassign(&mKey[i]); } } @@ -91,10 +88,7 @@ namespace MWGui // Check if quick keys are still valid for (int i=0; i<10; ++i) { - ItemWidget* button = mQuickKeyButtons[i]; - int type = mAssigned[i]; - - switch (type) + switch (mKey[i].type) { case Type_Unassigned: case Type_HandToHand: @@ -103,49 +97,55 @@ namespace MWGui case Type_Item: case Type_MagicItem: { - MWWorld::Ptr item = *button->getUserData(); + MWWorld::Ptr item = *mKey[i].button->getUserData(); // Make sure the item is available and is not broken - if (item.getRefData().getCount() < 1 || + if (!item || item.getRefData().getCount() < 1 || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) { // Try searching for a compatible replacement - std::string id = item.getCellRef().getRefId(); + item = store.findReplacement(mKey[i].id); + + if (item) + mKey[i].button->setUserData(MWWorld::Ptr(item)); - item = store.findReplacement(id); - button->setUserData(MWWorld::Ptr(item)); break; } } } } - } - void QuickKeysMenu::unassign(ItemWidget* key, int index) + void QuickKeysMenu::unassign(keyData* key) { - key->clearUserStrings(); - key->setItem(MWWorld::Ptr()); - while (key->getChildCount()) // Destroy number label - MyGUI::Gui::getInstance().destroyWidget(key->getChildAt(0)); + key->button->clearUserStrings(); + key->button->setItem(MWWorld::Ptr()); + + while (key->button->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(key->button->getChildAt(0)); - if (index == 9) + if (key->index == 10) { - mAssigned[index] = Type_HandToHand; + key->type = Type_HandToHand; - MyGUI::ImageBox* image = key->createWidget("ImageBox", + MyGUI::ImageBox* image = key->button->createWidget("ImageBox", MyGUI::IntCoord(14, 13, 32, 32), MyGUI::Align::Default); + image->setImageTexture("icons\\k\\stealth_handtohand.dds"); image->setNeedMouseFocus(false); } else { - mAssigned[index] = Type_Unassigned; + key->type = Type_Unassigned; + key->id = ""; + key->name = ""; + + MyGUI::TextBox* textBox = key->button->createWidgetReal("SandText", + MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); - MyGUI::TextBox* textBox = key->createWidgetReal("SandText", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); - textBox->setTextAlign (MyGUI::Align::Center); - textBox->setCaption (MyGUI::utility::toString(index+1)); - textBox->setNeedMouseFocus (false); + textBox->setTextAlign(MyGUI::Align::Center); + textBox->setCaption(MyGUI::utility::toString(key->index)); + textBox->setNeedMouseFocus(false); } } @@ -154,19 +154,24 @@ namespace MWGui int index = -1; for (int i = 0; i < 10; ++i) { - if (sender == mQuickKeyButtons[i] || sender->getParent () == mQuickKeyButtons[i]) + if (sender == mKey[i].button || sender->getParent() == mKey[i].button) { index = i; break; } } assert(index != -1); - mSelectedIndex = index; + mSelected = &mKey[index]; + + // prevent reallocation of zero key from Type_HandToHand + if(mSelected->index == 10) + return; // open assign dialog if (!mAssignDialog) mAssignDialog = new QuickKeysMenuAssign(this); - mAssignDialog->setVisible (true); + + mAssignDialog->setVisible(true); } void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender) @@ -174,10 +179,9 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_QuickKeysMenu); } - void QuickKeysMenu::onItemButtonClicked(MyGUI::Widget* sender) { - if (!mItemSelectionDialog ) + if (!mItemSelectionDialog) { mItemSelectionDialog = new ItemSelectionDialog("#{sQuickMenu6}"); mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItem); @@ -187,43 +191,45 @@ namespace MWGui mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyUsableItems); - mAssignDialog->setVisible (false); + mAssignDialog->setVisible(false); } void QuickKeysMenu::onMagicButtonClicked(MyGUI::Widget* sender) { - if (!mMagicSelectionDialog ) + if (!mMagicSelectionDialog) { mMagicSelectionDialog = new MagicSelectionDialog(this); } mMagicSelectionDialog->setVisible(true); - mAssignDialog->setVisible (false); + mAssignDialog->setVisible(false); } void QuickKeysMenu::onUnassignButtonClicked(MyGUI::Widget* sender) { - unassign(mQuickKeyButtons[mSelectedIndex], mSelectedIndex); - mAssignDialog->setVisible (false); + unassign(mSelected); + mAssignDialog->setVisible(false); } void QuickKeysMenu::onCancelButtonClicked(MyGUI::Widget* sender) { - mAssignDialog->setVisible (false); + mAssignDialog->setVisible(false); } void QuickKeysMenu::onAssignItem(MWWorld::Ptr item) { - assert (mSelectedIndex >= 0); - ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; - while (button->getChildCount()) // Destroy number label - MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); + assert(mSelected); + + while (mSelected->button->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0)); - mAssigned[mSelectedIndex] = Type_Item; + mSelected->type = Type_Item; + mSelected->id = item.getCellRef().getRefId(); + mSelected->name = item.getClass().getName(item); - button->setItem(item, ItemWidget::Barter); - button->setUserString ("ToolTipType", "ItemPtr"); - button->setUserData(MWWorld::Ptr(item)); + mSelected->button->setItem(item, ItemWidget::Barter); + mSelected->button->setUserString("ToolTipType", "ItemPtr"); + mSelected->button->setUserData(item); if (mItemSelectionDialog) mItemSelectionDialog->setVisible(false); @@ -234,40 +240,39 @@ namespace MWGui mItemSelectionDialog->setVisible(false); } - void QuickKeysMenu::onAssignMagicItem (MWWorld::Ptr item) + void QuickKeysMenu::onAssignMagicItem(MWWorld::Ptr item) { - assert (mSelectedIndex >= 0); - ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; - while (button->getChildCount()) // Destroy number label - MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); + assert(mSelected); + + while (mSelected->button->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0)); - mAssigned[mSelectedIndex] = Type_MagicItem; + mSelected->type = Type_MagicItem; - button->setFrame("textures\\menu_icon_select_magic_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); - button->setIcon(item); + mSelected->button->setFrame("textures\\menu_icon_select_magic_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); + mSelected->button->setIcon(item); - button->setUserString ("ToolTipType", "ItemPtr"); - button->setUserData(MWWorld::Ptr(item)); + mSelected->button->setUserString("ToolTipType", "ItemPtr"); + mSelected->button->setUserData(MWWorld::Ptr(item)); if (mMagicSelectionDialog) mMagicSelectionDialog->setVisible(false); } - void QuickKeysMenu::onAssignMagic (const std::string& spellId) + void QuickKeysMenu::onAssignMagic(const std::string& spellId) { - assert (mSelectedIndex >= 0); - ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; - while (button->getChildCount()) // Destroy number label - MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); + assert(mSelected); + while (mSelected->button->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0)); - mAssigned[mSelectedIndex] = Type_Magic; + mSelected->type = Type_Magic; + mSelected->id = spellId; - button->setItem(MWWorld::Ptr()); - button->setUserString ("ToolTipType", "Spell"); - button->setUserString ("Spell", spellId); + mSelected->button->setItem(MWWorld::Ptr()); + mSelected->button->setUserString("ToolTipType", "Spell"); + mSelected->button->setUserString("Spell", spellId); - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); // use the icon of the first effect const ESM::Spell* spell = esmStore.get().find(spellId); @@ -280,14 +285,14 @@ namespace MWGui path.insert(slashPos+1, "b_"); path = MWBase::Environment::get().getWindowManager()->correctIconPath(path); - button->setFrame("textures\\menu_icon_select_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); - button->setIcon(path); + mSelected->button->setFrame("textures\\menu_icon_select_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); + mSelected->button->setIcon(path); if (mMagicSelectionDialog) mMagicSelectionDialog->setVisible(false); } - void QuickKeysMenu::onAssignMagicCancel () + void QuickKeysMenu::onAssignMagicCancel() { mMagicSelectionDialog->setVisible(false); } @@ -295,18 +300,17 @@ namespace MWGui void QuickKeysMenu::updateActivatedQuickKey() { // there is no delayed action, nothing to do. - if (mActivatedIndex < 0) + if (!mActivated) return; - activateQuickKey(mActivatedIndex); + activateQuickKey(mActivated->index); } void QuickKeysMenu::activateQuickKey(int index) { - assert (index-1 >= 0); - ItemWidget* button = mQuickKeyButtons[index-1]; + assert(index >= 1 && index <= 10); - QuickKeyType type = mAssigned[index-1]; + keyData *key = &mKey[index-1]; MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); @@ -319,117 +323,103 @@ namespace MWGui || playerStats.getHitRecovery(); bool isReturnNeeded = playerStats.isParalyzed() || playerStats.isDead(); - if (isReturnNeeded && type != Type_Item) - { - return; - } - if (isDelayNeeded && type != Type_Item) - { - mActivatedIndex = index; + if (isReturnNeeded) return; - } + + else if (isDelayNeeded) + mActivated = key; + else - mActivatedIndex = -1; + mActivated = nullptr; - if (type == Type_Item || type == Type_MagicItem) + + if (key->type == Type_Item || key->type == Type_MagicItem) { - MWWorld::Ptr item = *button->getUserData(); - // Make sure the item is available and is not broken - if (item.getRefData().getCount() < 1 || - (item.getClass().hasItemHealth(item) && - item.getClass().getItemHealth(item) <= 0)) + MWWorld::Ptr item = *key->button->getUserData(); + + MWWorld::ContainerStoreIterator it = store.begin(); + for (; it != store.end(); ++it) { - // Try searching for a compatible replacement - std::string id = item.getCellRef().getRefId(); + if (*it == item) + break; + } + if (it == store.end()) + item = nullptr; - item = store.findReplacement(id); - button->setUserData(MWWorld::Ptr(item)); + // check the item is available and not broken + if (!item || item.getRefData().getCount() < 1 || + (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) + { + item = store.findReplacement(key->id); - if (item.getRefData().getCount() < 1) + if (!item || item.getRefData().getCount() < 1) { - // No replacement was found - MWBase::Environment::get().getWindowManager ()->messageBox ( - "#{sQuickMenu5} " + item.getClass().getName(item)); + MWBase::Environment::get().getWindowManager()->messageBox( + "#{sQuickMenu5} " + key->name); + return; } } - } - if (type == Type_Magic) - { - std::string spellId = button->getUserString("Spell"); - - // Make sure the player still has this spell - MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - MWMechanics::Spells& spells = stats.getSpells(); - if (!spells.hasSpell(spellId)) + if (key->type == Type_Item) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - MWBase::Environment::get().getWindowManager()->messageBox ( - "#{sQuickMenu5} " + spell->mName); - return; - } - store.setSelectedEnchantItem(store.end()); - MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell); - } - else if (type == Type_Item) - { - MWWorld::Ptr item = *button->getUserData(); - bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name(); - bool isTool = item.getTypeName() == typeid(ESM::Probe).name() || item.getTypeName() == typeid(ESM::Lockpick).name(); + bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name(); + bool isTool = item.getTypeName() == typeid(ESM::Probe).name() || + item.getTypeName() == typeid(ESM::Lockpick).name(); - // delay weapon switching if player is busy - if (isDelayNeeded && (isWeapon || isTool)) - { - mActivatedIndex = index; - return; - } + // delay weapon switching if player is busy + if (isDelayNeeded && (isWeapon || isTool)) + { + return; + } - // disable weapon switching if player is dead or paralyzed - if (isReturnNeeded && (isWeapon || isTool)) - { - return; + MWBase::Environment::get().getWindowManager()->useItem(item); + MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + // change draw state only if the item is in player's right hand + if (rightHand != store.end() && item == *rightHand) + { + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); + } } - - MWBase::Environment::get().getWindowManager()->useItem(item); - MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - // change draw state only if the item is in player's right hand - if (rightHand != store.end() && item == *rightHand) + else if (key->type == Type_MagicItem) { - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); + // equip, if it can be equipped + if (!item.getClass().getEquipmentSlots(item).first.empty()) + { + MWBase::Environment::get().getWindowManager()->useItem(item); + + // make sure that item was successfully equipped + if (!store.isEquipped(item)) + return; + } + + store.setSelectedEnchantItem(it); + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell); } } - else if (type == Type_MagicItem) + else if (key->type == Type_Magic) { - MWWorld::Ptr item = *button->getUserData(); + std::string spellId = key->id; - // retrieve ContainerStoreIterator to the item - MWWorld::ContainerStoreIterator it = store.begin(); - for (; it != store.end(); ++it) - { - if (*it == item) - { - break; - } - } - assert(it != store.end()); + // Make sure the player still has this spell + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + MWMechanics::Spells& spells = stats.getSpells(); - // equip, if it can be equipped - if (!item.getClass().getEquipmentSlots(item).first.empty()) + if (!spells.hasSpell(spellId)) { - MWBase::Environment::get().getWindowManager()->useItem(item); - - // make sure that item was successfully equipped - if (!store.isEquipped(item)) - return; + const ESM::Spell* spell = + MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + spell->mName); + return; } - store.setSelectedEnchantItem(it); + store.setSelectedEnchantItem(store.end()); + MWBase::Environment::get().getWindowManager() + ->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell); } - else if (type == Type_HandToHand) + else if (key->type == Type_HandToHand) { store.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, player); MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); @@ -491,9 +481,9 @@ namespace MWGui for (int i=0; i<10; ++i) { - ItemWidget* button = mQuickKeyButtons[i]; + ItemWidget* button = mKey[i].button; - int type = mAssigned[i]; + int type = mKey[i].type; ESM::QuickKeys::QuickKey key; key.mType = type; @@ -541,30 +531,29 @@ namespace MWGui if (i >= 10) return; - mSelectedIndex = i; - int keyType = it->mType; - std::string id = it->mId; - ItemWidget* button = mQuickKeyButtons[i]; + mSelected = &mKey[i]; + mSelected->type = (QuickKeysMenu::QuickKeyType) it->mType; + mSelected->id = it->mId; - switch (keyType) + switch (mSelected->type) { case Type_Magic: - if (MWBase::Environment::get().getWorld()->getStore().get().search(id)) - onAssignMagic(id); + if (MWBase::Environment::get().getWorld()->getStore().get().search(mSelected->id)) + onAssignMagic(mSelected->id); break; case Type_Item: case Type_MagicItem: { // Find the item by id - MWWorld::Ptr item = store.findReplacement(id); + MWWorld::Ptr item = store.findReplacement(mSelected->id); if (item.isEmpty()) - unassign(button, i); + unassign(&mKey[i]); else { - if (keyType == Type_Item) + if (mSelected->type == Type_Item) onAssignItem(item); - else if (keyType == Type_MagicItem) + else if (mSelected->type == Type_MagicItem) onAssignMagicItem(item); } @@ -572,7 +561,7 @@ namespace MWGui } case Type_Unassigned: case Type_HandToHand: - unassign(button, i); + unassign(&mKey[i]); break; } diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index b5bc60b19..431e847cb 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -55,23 +55,31 @@ namespace MWGui private: + + struct keyData { + int index; + ItemWidget* button; + QuickKeysMenu::QuickKeyType type; + std::string id; + std::string name; + keyData(): index(-1), button(nullptr), type(Type_Unassigned), id(""), name("") {} + }; + + std::vector mKey; + keyData* mSelected; + keyData* mActivated; + MyGUI::EditBox* mInstructionLabel; MyGUI::Button* mOkButton; - std::vector mQuickKeyButtons; - std::vector mAssigned; - QuickKeysMenuAssign* mAssignDialog; ItemSelectionDialog* mItemSelectionDialog; MagicSelectionDialog* mMagicSelectionDialog; - int mSelectedIndex; - int mActivatedIndex; - void onQuickKeyButtonClicked(MyGUI::Widget* sender); void onOkButtonClicked(MyGUI::Widget* sender); - void unassign(ItemWidget* key, int index); + void unassign(keyData* key); }; class QuickKeysMenuAssign : public WindowModal diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index b2991a034..20814aac5 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -191,6 +191,9 @@ namespace MWGui else if (type == "ItemPtr") { mFocusObject = *focus->getUserData(); + if (!mFocusObject) + return; + tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false, checkOwned()); } else if (type == "ItemModelIndex") diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 56901c95a..fde1f2ac9 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -56,15 +56,15 @@ namespace MWGui /* - * "Modal" windows cause the rest of the interface to be unaccessible while they are visible + * "Modal" windows cause the rest of the interface to be inaccessible while they are visible */ class WindowModal : public WindowBase { public: WindowModal(const std::string& parLayout); - virtual void onOpen(); - virtual void onClose(); - virtual bool exit() {return true;} + virtual void onOpen() override; + virtual void onClose() override; + virtual bool exit() override {return true;} }; /// A window that cannot be the target of a drag&drop action. diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index ac34c658b..6b45a513b 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -115,14 +115,16 @@ namespace MWMechanics if (Misc::Rng::roll0to99() < x) { - // Reduce shield durability by incoming damage - int shieldhealth = shield->getClass().getItemHealth(*shield); - - shieldhealth -= std::min(shieldhealth, int(damage)); - shield->getCellRef().setCharge(shieldhealth); - if (shieldhealth == 0) - inv.unequipItem(*shield, blocker); + if (!(weapon.isEmpty() && !attacker.getClass().isNpc())) // Unarmed creature attacks don't affect armor condition + { + // Reduce shield durability by incoming damage + int shieldhealth = shield->getClass().getItemHealth(*shield); + shieldhealth -= std::min(shieldhealth, int(damage)); + shield->getCellRef().setCharge(shieldhealth); + if (shieldhealth == 0) + inv.unequipItem(*shield, blocker); + } // Reduce blocker fatigue const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->getFloat(); const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->getFloat(); diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 7aef54007..7bedb1e37 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -328,7 +328,10 @@ namespace MWMechanics if (race->mData.mFlags & ESM::Race::Beast) return 0.f; } - // Intended fall-through + else + return 0.f; + + break; // Creatures can not wear armor case ESM::MagicEffect::BoundCuirass: case ESM::MagicEffect::BoundGloves: diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 29bc4692c..015a8b31b 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -608,7 +608,7 @@ namespace MWWorld void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) override; - void applyLoopingParticles(const MWWorld::Ptr& ptr); + void applyLoopingParticles(const MWWorld::Ptr& ptr) override; const std::vector& getContentFiles() const override; void breakInvisibility (const MWWorld::Ptr& actor) override; diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 7a01ce41d..0e43654f2 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -22,14 +22,14 @@ 0 0 630 - 746 + 791 - Game + Game Mechanics @@ -62,46 +62,6 @@ - - - - <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - - - Show effect duration - - - - - - - <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - - - Show enchant chance - - - - - - - <html><head/><body><p>If this setting is true, melee weapons reach and speed will be showed on item tooltip.</p><p>The default value is false.</p></body></html> - - - Show melee info - - - - - - - <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be showed on item tooltip.</p><p>The default value is false.</p></body></html> - - - Show projectile damage - - - @@ -112,64 +72,6 @@ - - - - <html><head/><body><p>Enable visual clues for items owned by NPCs when the crosshair is on the object.</p><p>The default value is Off.</p></body></html> - - - - -1 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Show owned: - - - - - - - 1 - - - - Off - - - - - Tool Tip Only - - - - - Crosshair Only - - - - - Tool Tip and Crosshair - - - - - - - @@ -357,6 +259,113 @@ + + + + User Interface + + + + + + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + + + Show effect duration + + + + + + + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + + + Show enchant chance + + + + + + + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be showed on item tooltip.</p><p>The default value is false.</p></body></html> + + + Show melee info + + + + + + + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be showed on item tooltip.</p><p>The default value is false.</p></body></html> + + + Show projectile damage + + + + + + + <html><head/><body><p>Enable visual clues for items owned by NPCs when the crosshair is on the object.</p><p>The default value is Off.</p></body></html> + + + + -1 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Show owned: + + + + + + + 1 + + + + Off + + + + + Tool Tip Only + + + + + Crosshair Only + + + + + Tool Tip and Crosshair + + + + + + + + + +