From 90b2709d6c0fa1997faf64127b3c85990e3610fd Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 25 Jul 2014 19:56:06 +0200 Subject: [PATCH 01/17] Allow absorption of non-harmful spells (Fixes #1693) Also fix absorption being calculated for each effect rather than the whole spell. --- apps/openmw/mwmechanics/spellcasting.cpp | 48 ++++++++++++------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 67da99200..2566bf189 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -287,6 +287,27 @@ namespace MWMechanics bool castByPlayer = (!caster.isEmpty() && caster.getRefData().getHandle() == "player"); + // Try absorbing if it's a spell + // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure + // if that is worth replicating. + bool absorbed = false; + if (spell && caster != target && target.getClass().isActor()) + { + int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude; + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + absorbed = (roll < absorb); + if (absorbed) + { + const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); + MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( + "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, ""); + // Magicka is increased by cost of spell + DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); + magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); + target.getClass().getCreatureStats(target).setMagicka(magicka); + } + } + for (std::vector::const_iterator effectIt (effects.mList.begin()); effectIt!=effects.mList.end(); ++effectIt) { @@ -326,31 +347,13 @@ namespace MWMechanics { anyHarmfulEffect = true; + if (absorbed) // Absorbed, and we know there was a harmful effect (figuring that out is the only reason we are in this loop) + break; + // If player is attempting to cast a harmful spell, show the target's HP bar if (castByPlayer && target != caster) MWBase::Environment::get().getWindowManager()->setEnemy(target); - // Try absorbing if it's a spell - // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure - // if that is worth replicating. - if (spell && caster != target) - { - int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude; - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - bool isAbsorbed = (roll < absorb); - if (isAbsorbed) - { - const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); - MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( - "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::Reflect, false, ""); - // Magicka is increased by cost of spell - DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); - magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); - target.getClass().getCreatureStats(target).setMagicka(magicka); - magnitudeMult = 0; - } - } - // Try reflecting if (!reflected && magnitudeMult > 0 && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) { @@ -382,8 +385,7 @@ namespace MWMechanics } } - - if (magnitudeMult > 0) + if (magnitudeMult > 0 && !absorbed) { float random = std::rand() / static_cast(RAND_MAX); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; From 4a1e5610153eb1be6f72dcd730084b609b83753c Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 25 Jul 2014 23:03:34 +0200 Subject: [PATCH 02/17] Properly assign effect attribute/skill in potion creation (Fixes #1704) --- apps/openmw/mwmechanics/alchemy.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 2e03122d5..a097446a0 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -181,7 +181,13 @@ void MWMechanics::Alchemy::updateEffects() ESM::ENAMstruct effect; effect.mEffectID = iter->mId; - effect.mSkill = effect.mAttribute = iter->mArg; // somewhat hack-ish, but should work + effect.mAttribute = -1; + effect.mSkill = -1; + + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) + effect.mSkill = iter->mArg; + else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) + effect.mAttribute = iter->mArg; effect.mRange = 0; effect.mArea = 0; From b370c0f7b5c29a45fca9f923d78649651ba96fbd Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 00:57:49 +0200 Subject: [PATCH 03/17] Enchanting: Don't check price on self-enchanting (Bug #1701) --- apps/openmw/mwgui/enchantingdialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 92221977b..145602eb0 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -299,7 +299,7 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - if (mEnchanting.getEnchantPrice() > playerGold) + if (mPtr != player && mEnchanting.getEnchantPrice() > playerGold) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); return; From 09607f992ea2f725e886dc193813f88b3bbe7fd9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 00:59:35 +0200 Subject: [PATCH 04/17] Enchanting: fix inverted self-enchant success chance (Fixes #1701) --- apps/openmw/mwmechanics/enchanting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index f3f6795db..18732514b 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -62,7 +62,7 @@ namespace MWMechanics if(mSelfEnchanting) { - if(getEnchantChance() (RAND_MAX)*100) + if(std::rand()/static_cast (RAND_MAX)*100 < getEnchantChance()) return false; mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); From 9c60e4d8265c0e1dd54499790de491c7e3929e7b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 01:03:27 +0200 Subject: [PATCH 05/17] Change button caption from "Buy" to "Create" when self-enchanting --- apps/openmw/mwgui/enchantingdialog.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 145602eb0..1fef34371 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -144,6 +144,8 @@ namespace MWGui mEnchanting.setSelfEnchanting(false); mEnchanting.setEnchanter(actor); + mBuyButton->setCaptionWithReplacing("#{sBuy}"); + mPtr = actor; startEditing (); @@ -156,6 +158,8 @@ namespace MWGui mEnchanting.setSelfEnchanting(true); mEnchanting.setEnchanter(player); + mBuyButton->setCaptionWithReplacing("#{sCreate}"); + mPtr = player; startEditing(); mEnchanting.setSoulGem(soulgem); From 16b089cdc833e332bba1243cfb7ee9328aa75324 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 01:07:32 +0200 Subject: [PATCH 06/17] Fix invisible enchanting price when self-enchanting was previously used. --- apps/openmw/mwgui/enchantingdialog.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 1fef34371..d7e79e7a2 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -149,6 +149,9 @@ namespace MWGui mPtr = actor; startEditing (); + mPrice->setVisible(true); + mPriceText->setVisible(true); + updateLabels(); } void EnchantingDialog::startSelfEnchanting(MWWorld::Ptr soulgem) @@ -162,7 +165,6 @@ namespace MWGui mPtr = player; startEditing(); - mEnchanting.setSoulGem(soulgem); setSoulGem(soulgem); From 31d058b98cf7a17a9081768d36b60309afabbd6b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 02:23:42 +0200 Subject: [PATCH 07/17] Add workaround for ScrollView messing up canvas size (Fixes #1700) TODO: Create fixed ScrollView widget? --- apps/openmw/mwgui/itemview.cpp | 6 ++++++ apps/openmw/mwgui/journalwindow.cpp | 3 +++ apps/openmw/mwgui/list.cpp | 4 ++++ apps/openmw/mwgui/merchantrepair.cpp | 3 +++ apps/openmw/mwgui/quickkeysmenu.cpp | 5 +++-- apps/openmw/mwgui/recharge.cpp | 4 ++++ apps/openmw/mwgui/repair.cpp | 3 +++ apps/openmw/mwgui/review.cpp | 3 +++ apps/openmw/mwgui/scrollwindow.cpp | 3 +++ apps/openmw/mwgui/settingswindow.cpp | 3 +++ apps/openmw/mwgui/spellbuyingwindow.cpp | 5 +++++ apps/openmw/mwgui/spellcreationdialog.cpp | 3 +++ apps/openmw/mwgui/spellwindow.cpp | 3 +++ apps/openmw/mwgui/statswindow.cpp | 6 ++++++ apps/openmw/mwgui/travelwindow.cpp | 3 +++ 15 files changed, 55 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index b86f61034..c4c6c6545 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -115,7 +115,13 @@ void ItemView::update() } x += 42; MyGUI::IntSize size = MyGUI::IntSize(std::max(mScrollView->getSize().width, x), mScrollView->getSize().height); + + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mScrollView->setVisibleVScroll(false); + mScrollView->setVisibleHScroll(false); mScrollView->setCanvasSize(size); + mScrollView->setVisibleVScroll(true); + mScrollView->setVisibleHScroll(true); dragArea->setSize(size); } diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index fa27b4ef0..b588ba3e1 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -400,7 +400,10 @@ namespace getPage (pageId)->showPage (book, 0); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + getWidget (listId)->setVisibleVScroll(false); getWidget (listId)->setCanvasSize (size.first, size.second); + getWidget (listId)->setVisibleVScroll(true); } void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId character) diff --git a/apps/openmw/mwgui/list.cpp b/apps/openmw/mwgui/list.cpp index b0c514b9d..e88d43534 100644 --- a/apps/openmw/mwgui/list.cpp +++ b/apps/openmw/mwgui/list.cpp @@ -93,7 +93,11 @@ namespace MWGui } ++i; } + + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mScrollView->setVisibleVScroll(false); mScrollView->setCanvasSize(mClient->getSize().width, std::max(mItemHeight, mClient->getSize().height)); + mScrollView->setVisibleVScroll(true); if (!scrollbarShown && mItemHeight > mClient->getSize().height) redraw(true); diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 0d1a57000..013a40017 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -91,7 +91,10 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick); } } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mList->setVisibleVScroll(false); mList->setCanvasSize (MyGUI::IntSize(mList->getWidth(), std::max(mList->getHeight(), currentY))); + mList->setVisibleVScroll(true); mGoldLabel->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 328ef21fb..84150c322 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -654,9 +654,10 @@ namespace MWGui mHeight += spellHeight; } - + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mMagicList->setVisibleVScroll(false); mMagicList->setCanvasSize (mWidth, std::max(mMagicList->getHeight(), mHeight)); - + mMagicList->setVisibleVScroll(true); } void MagicSelectionDialog::addGroup(const std::string &label, const std::string& label2) diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index 9c43b1416..1b994d584 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -119,7 +119,11 @@ void Recharge::updateView() currentY += 32 + 4; } + + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mView->setVisibleVScroll(false); mView->setCanvasSize (MyGUI::IntSize(mView->getWidth(), std::max(mView->getHeight(), currentY))); + mView->setVisibleVScroll(true); } void Recharge::onCancel(MyGUI::Widget *sender) diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index 986e27243..019f341b5 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -126,7 +126,10 @@ void Repair::updateRepairView() currentY += 32 + 4; } } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mRepairView->setVisibleVScroll(false); mRepairView->setCanvasSize (MyGUI::IntSize(mRepairView->getWidth(), std::max(mRepairView->getHeight(), currentY))); + mRepairView->setVisibleVScroll(true); } void Repair::onCancel(MyGUI::Widget *sender) diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index e27e40ae6..072411707 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -320,7 +320,10 @@ namespace MWGui if (!mMiscSkills.empty()) addSkills(mMiscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSkillView->setVisibleVScroll(false); mSkillView->setCanvasSize (mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top)); + mSkillView->setVisibleVScroll(true); } // widget controls diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index 3d0751114..867bef0d7 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -57,10 +57,13 @@ namespace MWGui BookTextParser parser; MyGUI::IntSize size = parser.parseScroll(ref->mBase->mText, mTextView, 390); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mTextView->setVisibleVScroll(false); if (size.height > mTextView->getSize().height) mTextView->setCanvasSize(MyGUI::IntSize(410, size.height)); else mTextView->setCanvasSize(410, mTextView->getSize().height); + mTextView->setVisibleVScroll(true); mTextView->setViewOffset(MyGUI::IntPoint(0,0)); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 09d2fc19c..abeb6b86c 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -473,7 +473,10 @@ namespace MWGui curH += h; } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mControlsBox->setVisibleVScroll(false); mControlsBox->setCanvasSize (mControlsBox->getWidth(), std::max(curH, mControlsBox->getHeight())); + mControlsBox->setVisibleVScroll(true); } void SettingsWindow::onRebindAction(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index 8d9f35daa..d6c716453 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -50,6 +50,8 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + // TODO: refactor to use MyGUI::ListBox + MyGUI::Button* toAdd = mSpellsView->createWidget( "SandTextButton", @@ -106,7 +108,10 @@ namespace MWGui updateLabels(); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSpellsView->setVisibleVScroll(false); mSpellsView->setCanvasSize (MyGUI::IntSize(mSpellsView->getWidth(), std::max(mSpellsView->getHeight(), mCurrentY))); + mSpellsView->setVisibleVScroll(true); } bool SpellBuyingWindow::playerHasSpell(const std::string &id) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 030b8bf37..f1ca3c802 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -637,7 +637,10 @@ namespace MWGui ++i; } + // Canvas size must be expressed with HScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mUsedEffectsView->setVisibleHScroll(false); mUsedEffectsView->setCanvasSize(size); + mUsedEffectsView->setVisibleHScroll(true); notifyEffectsChanged(); } diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 77da56fa4..15b5bf280 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -261,7 +261,10 @@ namespace MWGui mHeight += spellHeight; } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSpellView->setVisibleVScroll(false); mSpellView->setCanvasSize(mSpellView->getWidth(), std::max(mSpellView->getHeight(), mHeight)); + mSpellView->setVisibleVScroll(true); } void SpellWindow::addGroup(const std::string &label, const std::string& label2) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index b11258f1c..7f3b66d83 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -82,7 +82,10 @@ namespace MWGui { mLeftPane->setCoord( MyGUI::IntCoord(0, 0, 0.44*window->getSize().width, window->getSize().height) ); mRightPane->setCoord( MyGUI::IntCoord(0.44*window->getSize().width, 0, 0.56*window->getSize().width, window->getSize().height) ); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSkillView->setVisibleVScroll(false); mSkillView->setCanvasSize (mSkillView->getWidth(), mSkillView->getCanvasSize().height); + mSkillView->setVisibleVScroll(true); } void StatsWindow::setBar(const std::string& name, const std::string& tname, int val, int max) @@ -578,7 +581,10 @@ namespace MWGui mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sCrimeHelp}"); } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSkillView->setVisibleVScroll(false); mSkillView->setCanvasSize (mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top)); + mSkillView->setVisibleVScroll(true); } void StatsWindow::onPinToggled() diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 9aa75173a..d874e8c0c 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -126,7 +126,10 @@ namespace MWGui } updateLabels(); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mDestinationsView->setVisibleVScroll(false); mDestinationsView->setCanvasSize (MyGUI::IntSize(mDestinationsView->getWidth(), std::max(mDestinationsView->getHeight(), mCurrentY))); + mDestinationsView->setVisibleVScroll(true); } void TravelWindow::onTravelButtonClick(MyGUI::Widget* _sender) From fdc6dd6985959a24b4c4f0b29e16e7717201f79a Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 15:34:42 +0200 Subject: [PATCH 08/17] Shader compatibility fixes for GLSL ES --- files/materials/atmosphere.shader | 2 +- files/materials/clouds.shader | 2 +- files/materials/mygui.shader | 2 +- files/materials/objects.shader | 16 ++++++++-------- files/materials/terrain.shader | 24 ++++++++++++------------ 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/files/materials/atmosphere.shader b/files/materials/atmosphere.shader index f02c5766f..c8a8e0220 100644 --- a/files/materials/atmosphere.shader +++ b/files/materials/atmosphere.shader @@ -33,7 +33,7 @@ SH_START_PROGRAM { - shOutputColour(0) = alphaFade * atmosphereColour + (1.f - alphaFade) * horizonColour; + shOutputColour(0) = alphaFade * atmosphereColour + (1.0 - alphaFade) * horizonColour; } #endif diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader index d3e5f083c..9e9b10256 100644 --- a/files/materials/clouds.shader +++ b/files/materials/clouds.shader @@ -25,7 +25,7 @@ shOutputPosition = shMatrixMult(proj, shMatrixMult(worldviewFixed, shInputPosition)); UV = uv0; - alphaFade = (shInputPosition.z <= 200.f) ? ((shInputPosition.z <= 100.f) ? 0.0 : 0.25) : 1.0; + alphaFade = (shInputPosition.z <= 200.0) ? ((shInputPosition.z <= 100.0) ? 0.0 : 0.25) : 1.0; } #else diff --git a/files/materials/mygui.shader b/files/materials/mygui.shader index 014558d97..4d12eba90 100644 --- a/files/materials/mygui.shader +++ b/files/materials/mygui.shader @@ -14,7 +14,7 @@ SH_START_PROGRAM { - shOutputPosition = float4(shInputPosition.xyz, 1.f); + shOutputPosition = float4(shInputPosition.xyz, 1.0); #if TEXTURE UV.xy = uv0; #endif diff --git a/files/materials/objects.shader b/files/materials/objects.shader index ed75babdd..bacf3f912 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -210,11 +210,11 @@ #if VERTEXCOLOR_MODE == 2 lightResult.xyz += colour.xyz * lightDiffuse[@shIterator].xyz * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(viewNormal.xyz, lightDir), 0); + * max(dot(viewNormal.xyz, lightDir), 0.0); #else lightResult.xyz += materialDiffuse.xyz * lightDiffuse[@shIterator].xyz * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(viewNormal.xyz, lightDir), 0); + * max(dot(viewNormal.xyz, lightDir), 0.0); #endif #if @shIterator == 0 @@ -432,11 +432,11 @@ #if VERTEXCOLOR_MODE == 2 lightResult.xyz += colourPassthrough.xyz * lightDiffuse[@shIterator].xyz * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(viewNormal.xyz, lightDir), 0); + * max(dot(viewNormal.xyz, lightDir), 0.0); #else lightResult.xyz += materialDiffuse.xyz * lightDiffuse[@shIterator].xyz * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(viewNormal.xyz, lightDir), 0); + * max(dot(viewNormal.xyz, lightDir), 0.0); #endif #if @shIterator == 0 @@ -504,8 +504,8 @@ #if ENV_MAP // Everything looks better with fresnel - float facing = 1.0 - max(abs(dot(-eyeDir, normal)), 0); - float envFactor = shSaturate(0.25 + 0.75 * pow(facing, 1)); + float facing = 1.0 - max(abs(dot(-eyeDir, normal)), 0.0); + float envFactor = shSaturate(0.25 + 0.75 * pow(facing, 1.0)); shOutputColour(0).xyz += shSample(envMap, UV.zw).xyz * envFactor * env_map_color; #endif @@ -513,7 +513,7 @@ #if SPECULAR float3 light0Dir = normalize(lightPosObjSpace0.xyz); - float NdotL = max(dot(normal, light0Dir), 0); + float NdotL = max(dot(normal, light0Dir), 0.0); float3 halfVec = normalize (light0Dir + eyeDir); float shininess = matShininess; @@ -522,7 +522,7 @@ shininess *= (specTex.a); #endif - float3 specular = pow(max(dot(normal, halfVec), 0), shininess) * lightSpec0 * matSpec; + float3 specular = pow(max(dot(normal, halfVec), 0.0), shininess) * lightSpec0 * matSpec; #if SPEC_MAP specular *= specTex.xyz; #else diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 1436de0c3..8384588e4 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -175,7 +175,7 @@ lightResult.xyz += lightDiffuse[@shIterator].xyz * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(normal.xyz, lightDir), 0); + * max(dot(normal.xyz, lightDir), 0.0); #if @shIterator == 0 directionalResult = lightResult.xyz; @@ -310,7 +310,7 @@ shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) #if !IS_FIRST_PASS // Opacity the previous passes should have, i.e. 1 - (opacity of this pass) -float previousAlpha = 1.f; +float previousAlpha = 1.0; #endif @@ -334,7 +334,7 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; float4 albedo = float4(0,0,0,1); - float2 layerUV = float2(UV.x, 1.f-UV.y) * 16; // Reverse Y, required to get proper tangents + float2 layerUV = float2(UV.x, 1.0-UV.y) * 16.0; // Reverse Y, required to get proper tangents float2 thisLayerUV; float4 normalTex; float4 diffuseTex; @@ -349,9 +349,9 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; #if @shPropertyBool(use_normal_map_@shIterator) normalTex = shSample(normalMap@shIterator, thisLayerUV); #if @shIterator == 0 && IS_FIRST_PASS - TSnormal = normalize(normalTex.xyz * 2 - 1); + TSnormal = normalize(normalTex.xyz * 2.0 - 1.0); #else - TSnormal = shLerp(TSnormal, normalTex.xyz * 2 - 1, blendValues@shPropertyString(blendmap_component_@shIterator)); + TSnormal = shLerp(TSnormal, normalTex.xyz * 2.0 - 1.0, blendValues@shPropertyString(blendmap_component_@shIterator)); #endif #endif @@ -361,7 +361,7 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; diffuseTex = shSample(diffuseMap@shIterator, layerUV); #if !@shPropertyBool(use_specular_@shIterator) - diffuseTex.a = 0; + diffuseTex.a = 0.0; #endif #if @shIterator == 0 @@ -371,7 +371,7 @@ albedo = shLerp(albedo, diffuseTex, blendValues@shPropertyString(blendmap_compon #endif #if !IS_FIRST_PASS - previousAlpha *= 1.f-blendValues@shPropertyString(blendmap_component_@shIterator); + previousAlpha *= 1.0-blendValues@shPropertyString(blendmap_component_@shIterator); #endif @@ -404,7 +404,7 @@ albedo = shLerp(albedo, diffuseTex, blendValues@shPropertyString(blendmap_compon lightResult.xyz += lightDiffuse[@shIterator].xyz * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(normal.xyz, lightDir), 0); + * max(dot(normal.xyz, lightDir), 0.0); #if @shIterator == 0 float3 directionalResult = lightResult.xyz; #endif @@ -444,10 +444,10 @@ albedo = shLerp(albedo, diffuseTex, blendValues@shPropertyString(blendmap_compon // Specular float3 light0Dir = normalize(lightPos0.xyz); - float NdotL = max(dot(normal, light0Dir), 0); + float NdotL = max(dot(normal, light0Dir), 0.0); float3 halfVec = normalize (light0Dir + eyeDir); - float3 specular = pow(max(dot(normal, halfVec), 0), 32) * lightSpec0; + float3 specular = pow(max(dot(normal, halfVec), 0.0), 32.0) * lightSpec0; shOutputColour(0).xyz += specular * (albedo.a) * shadow; #endif @@ -465,9 +465,9 @@ albedo = shLerp(albedo, diffuseTex, blendValues@shPropertyString(blendmap_compon shOutputColour(0).xyz = max(shOutputColour(0).xyz, float3(0,0,0)); #if IS_FIRST_PASS - shOutputColour(0).a = 1; + shOutputColour(0).a = 1.0; #else - shOutputColour(0).a = 1.f-previousAlpha; + shOutputColour(0).a = 1.0-previousAlpha; #endif } From 8c81e22f3e3c92905e5ec21c30d439ef225822c3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 18:01:53 +0200 Subject: [PATCH 09/17] Determine target for On Touch effects for non-player actors --- apps/openmw/mwworld/worldimp.cpp | 86 ++++++++++++++++++++++--------- apps/openmw/mwworld/worldimp.hpp | 2 +- libs/openengine/bullet/physic.cpp | 4 +- libs/openengine/bullet/physic.hpp | 4 +- 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 9b747582f..10c2e4974 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -148,7 +148,7 @@ namespace MWWorld mSky (true), mCells (mStore, mEsm), mActivationDistanceOverride (activationDistanceOverride), mFallback(fallbackMap), mTeleportEnabled(true), mLevitationEnabled(true), - mFacedDistance(FLT_MAX), mGodMode(false), mContentFiles (contentFiles), + mGodMode(false), mContentFiles (contentFiles), mGoToJail(false), mStartCell (startCell), mStartupScript(startupScript) { @@ -290,7 +290,6 @@ namespace MWWorld mGodMode = false; mSky = true; mTeleportEnabled = true; - mFacedDistance = FLT_MAX; mGlobalVariables.fill (mStore); } @@ -1462,30 +1461,22 @@ namespace MWWorld updateFacedHandle (); } - void World::updateFacedHandle () + void World::getFacedHandle(std::string& facedHandle, float maxDistance) { - float telekinesisRangeBonus = - mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).getMagicEffects() - .get(ESM::MagicEffect::Telekinesis).mMagnitude; - telekinesisRangeBonus = feetToGameUnits(telekinesisRangeBonus); + maxDistance += mRendering->getCameraDistance(); - float activationDistance = getMaxActivationDistance() + telekinesisRangeBonus; - activationDistance += mRendering->getCameraDistance(); - - // send new query - // figure out which object we want to test against std::vector < std::pair < float, std::string > > results; if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { float x, y; MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); - results = mPhysics->getFacedHandles(x, y, activationDistance); + results = mPhysics->getFacedHandles(x, y, maxDistance); if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) results = mPhysics->getFacedHandles(x, y, getMaxActivationDistance ()*50); } else { - results = mPhysics->getFacedHandles(activationDistance); + results = mPhysics->getFacedHandles(maxDistance); } // ignore the player and other things we're not interested in @@ -1508,15 +1499,21 @@ namespace MWWorld if (results.empty() || results.front().second.find("HeightField") != std::string::npos) // Blocked by terrain - { - mFacedHandle = ""; - mFacedDistance = FLT_MAX; - } + facedHandle = ""; else - { - mFacedHandle = results.front().second; - mFacedDistance = results.front().first; - } + facedHandle = results.front().second; + } + + void World::updateFacedHandle () + { + float telekinesisRangeBonus = + mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).getMagicEffects() + .get(ESM::MagicEffect::Telekinesis).mMagnitude; + telekinesisRangeBonus = feetToGameUnits(telekinesisRangeBonus); + + float activationDistance = getMaxActivationDistance() + telekinesisRangeBonus; + + getFacedHandle(mFacedHandle, activationDistance); } bool World::isCellExterior() const @@ -2344,8 +2341,49 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - // TODO: this only works for the player - MWWorld::Ptr target = getFacedObject(); + // Get the target to use for "on touch" effects + MWWorld::Ptr target; + float distance = 192.f; // ?? + + if (actor == getPlayerPtr()) + { + // For the player, use camera to aim + std::string facedHandle; + getFacedHandle(facedHandle, distance); + if (!facedHandle.empty()) + target = getPtrViaHandle(facedHandle); + } + else + { + // For NPCs use facing direction from Head node + Ogre::Vector3 origin(actor.getRefData().getPosition().pos); + MWRender::Animation *anim = mRendering->getAnimation(actor); + if(anim != NULL) + { + Ogre::Node *node = anim->getNode("Head"); + if (node == NULL) + node = anim->getNode("Bip01 Head"); + if(node != NULL) + origin += node->_getDerivedPosition(); + } + Ogre::Quaternion orient; + orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + Ogre::Vector3 direction = orient.yAxis(); + Ogre::Vector3 dest = origin + direction * distance; + + + std::vector > collisions = mPhysEngine->rayTest2(btVector3(origin.x, origin.y, origin.z), btVector3(dest.x, dest.y, dest.z)); + for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end(); ++cIt) + { + MWWorld::Ptr collided = getPtrViaHandle(cIt->second); + if (collided != actor) + { + target = collided; + break; + } + } + } std::string selectedSpell = stats.getSpells().getSelectedSpell(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index dda442963..5c44388ca 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -92,7 +92,6 @@ namespace MWWorld int mActivationDistanceOverride; std::string mFacedHandle; - float mFacedDistance; std::string mStartupScript; @@ -114,6 +113,7 @@ namespace MWWorld void updateWindowManager (); void performUpdateSceneQueries (); void updateFacedHandle (); + void getFacedHandle(std::string& facedHandle, float maxDistance); float getMaxActivationDistance (); float getNpcActivationDistance (); diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 6bc544af4..954d7c283 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -737,7 +737,7 @@ namespace Physic { } - std::pair PhysicEngine::rayTest(btVector3& from,btVector3& to,bool raycastingObjectOnly,bool ignoreHeightMap, Ogre::Vector3* normal) + std::pair PhysicEngine::rayTest(const btVector3 &from, const btVector3 &to, bool raycastingObjectOnly, bool ignoreHeightMap, Ogre::Vector3* normal) { std::string name = ""; float d = -1; @@ -801,7 +801,7 @@ namespace Physic return std::make_pair(false, 1); } - std::vector< std::pair > PhysicEngine::rayTest2(btVector3& from, btVector3& to) + std::vector< std::pair > PhysicEngine::rayTest2(const btVector3& from, const btVector3& to) { MyRayResultCallback resultCallback1; resultCallback1.m_collisionFilterGroup = 0xff; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index e37caee38..09bff4b04 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -302,13 +302,13 @@ namespace Physic * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). * If \a normal is non-NULL, the hit normal will be written there (if there is a hit) */ - std::pair rayTest(btVector3& from,btVector3& to,bool raycastingObjectOnly = true, + std::pair rayTest(const btVector3& from,const btVector3& to,bool raycastingObjectOnly = true, bool ignoreHeightMap = false, Ogre::Vector3* normal = NULL); /** * Return all objects hit by a ray. */ - std::vector< std::pair > rayTest2(btVector3& from, btVector3& to); + std::vector< std::pair > rayTest2(const btVector3 &from, const btVector3 &to); std::pair sphereCast (float radius, btVector3& from, btVector3& to); ///< @return (hit, relative distance) From d7acb7fc7dfc0f462232732f9d3fcf4d0f7793ee Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 22:24:05 +0200 Subject: [PATCH 10/17] Ignore invalid shader cache index (Bug #1664) --- extern/shiny/Main/Factory.cpp | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/extern/shiny/Main/Factory.cpp b/extern/shiny/Main/Factory.cpp index 72af07d58..eab5c8dfc 100644 --- a/extern/shiny/Main/Factory.cpp +++ b/extern/shiny/Main/Factory.cpp @@ -51,24 +51,32 @@ namespace sh { assert(mCurrentLanguage != Language_None); - if (boost::filesystem::exists (mPlatform->getCacheFolder () + "/lastModified.txt")) + try { - std::ifstream file; - file.open(std::string(mPlatform->getCacheFolder () + "/lastModified.txt").c_str()); - - std::string line; - while (getline(file, line)) + if (boost::filesystem::exists (mPlatform->getCacheFolder () + "/lastModified.txt")) { - std::string sourceFile = line; + std::ifstream file; + file.open(std::string(mPlatform->getCacheFolder () + "/lastModified.txt").c_str()); + + std::string line; + while (getline(file, line)) + { + std::string sourceFile = line; - if (!getline(file, line)) - assert(0); + if (!getline(file, line)) + assert(0); - int modified = boost::lexical_cast(line); + int modified = boost::lexical_cast(line); - mShadersLastModified[sourceFile] = modified; + mShadersLastModified[sourceFile] = modified; + } } } + catch (std::exception& e) + { + std::cerr << "Failed to load shader modification index: " << e.what() << std::endl; + mShadersLastModified.clear(); + } // load configurations { From 47e42d4fda6bdadb67b6b1eadcd893da172cf3e0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 22:29:04 +0200 Subject: [PATCH 11/17] Destroy Engine after exception is logged In cases where OpenMW throws an exception, then crashes in the Engine destructor (ideally should not happen, but keeps happening), we will at least see what the exception was about. --- apps/openmw/main.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index adde408b9..f91d7de7a 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -309,6 +309,8 @@ int main(int argc, char**argv) boost::filesystem::ofstream logfile; + std::auto_ptr engine; + int ret = 0; try { @@ -350,11 +352,11 @@ int main(int argc, char**argv) boost::filesystem::current_path(bundlePath); #endif - OMW::Engine engine(cfgMgr); + engine.reset(new OMW::Engine(cfgMgr)); - if (parseOptions(argc, argv, engine, cfgMgr)) + if (parseOptions(argc, argv, *engine, cfgMgr)) { - engine.go(); + engine->go(); } } catch (std::exception &e) From 1a04501951c6dce2f7b762e619bc3daa24cfcabe Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 27 Jul 2014 18:53:54 +0200 Subject: [PATCH 12/17] Handle faction save/load properly when player has faction reputation in a faction he is not a member of (Fixes #1573) --- components/esm/npcstats.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index 3fa954182..26d8f9596 100644 --- a/components/esm/npcstats.cpp +++ b/components/esm/npcstats.cpp @@ -4,7 +4,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -ESM::NpcStats::Faction::Faction() : mExpelled (false), mRank (0), mReputation (0) {} +ESM::NpcStats::Faction::Faction() : mExpelled (false), mRank (-1), mReputation (0) {} void ESM::NpcStats::load (ESMReader &esm) { @@ -98,7 +98,7 @@ void ESM::NpcStats::save (ESMWriter &esm) const esm.writeHNT ("FAEX", expelled); } - if (iter->second.mRank) + if (iter->second.mRank >= 0) esm.writeHNT ("FARA", iter->second.mRank); if (iter->second.mReputation) From d81e9cfefd179c8498325baf3bb231ec9fd10a07 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 27 Jul 2014 20:30:52 +0200 Subject: [PATCH 13/17] Implement actors fighting for the actor they are following (Fixes #1141) --- apps/openmw/mwclass/creature.cpp | 13 ++- apps/openmw/mwmechanics/actors.cpp | 84 +++++++++---------- apps/openmw/mwmechanics/aicombat.cpp | 2 + apps/openmw/mwmechanics/aifollow.cpp | 5 ++ apps/openmw/mwmechanics/aifollow.hpp | 2 + apps/openmw/mwmechanics/aisequence.cpp | 10 +++ apps/openmw/mwmechanics/aisequence.hpp | 4 + .../mwmechanics/mechanicsmanagerimp.cpp | 2 + apps/openmw/mwworld/class.hpp | 6 -- 9 files changed, 77 insertions(+), 51 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index a74ee6fd6..c175ce3eb 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -366,10 +366,17 @@ namespace MWClass getCreatureStats(ptr).setAttacked(true); // Self defense - if (!attacker.isEmpty() && !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker) - && (canWalk(ptr) || canFly(ptr) || canSwim(ptr))) // No retaliation for totally static creatures - // (they have no movement or attacks anyway) + if ( ((!attacker.isEmpty() && attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(ptr)) + || attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker) + && (canWalk(ptr) || canFly(ptr) || canSwim(ptr)) // No retaliation for totally static creatures + // (they have no movement or attacks anyway) + ) + { + // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. + // Note: accidental or collateral damage attacks are ignored. MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker); + } if(!successful) { diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 8f1209827..36397ac09 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -192,6 +192,15 @@ namespace MWMechanics CreatureStats& creatureStats = actor1.getClass().getCreatureStats(actor1); if (againstPlayer && creatureStats.isHostile()) return; // already fighting against player + if (actor2.getClass().getCreatureStats(actor2).isDead() + || actor1.getClass().getCreatureStats(actor1).isDead()) + return; + + const ESM::Position& actor1Pos = actor2.getRefData().getPosition(); + const ESM::Position& actor2Pos = actor2.getRefData().getPosition(); + float sqrDist = Ogre::Vector3(actor1Pos.pos).squaredDistance(Ogre::Vector3(actor2Pos.pos)); + if (sqrDist > 7168*7168) + return; // pure water creatures won't try to fight with the target on the ground // except that creature is already hostile @@ -208,9 +217,9 @@ namespace MWMechanics else { aggressive = false; - // if one of actors is creature then we should make a decision to start combat or not - // NOTE: function doesn't take into account combat between 2 creatures - if (!actor1.getClass().isNpc()) + + // Make guards fight aggressive creatures + if (!actor1.getClass().isNpc() && actor2.getClass().isClass(actor2, "Guard")) { // if creature is hostile then it is necessarily to start combat if (creatureStats.isHostile()) aggressive = true; @@ -218,24 +227,34 @@ namespace MWMechanics } } + // start combat if we are in combat with any followers of this actor + const std::list& followers = getActorsFollowing(actor2); + for (std::list::const_iterator it = followers.begin(); it != followers.end(); ++it) + { + if (creatureStats.getAiSequence().isInCombat(*it)) + aggressive = true; + } + // start combat if we are in combat with someone this actor is following + const CreatureStats& creatureStats2 = actor2.getClass().getCreatureStats(actor2); + for (std::list::const_iterator it = creatureStats2.getAiSequence().begin(); it != creatureStats2.getAiSequence().end(); ++it) + { + if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow && + creatureStats.getAiSequence().isInCombat(dynamic_cast(*it)->getTarget())) + aggressive = true; + } + if(aggressive) { - const ESM::Position& actor1Pos = actor2.getRefData().getPosition(); - const ESM::Position& actor2Pos = actor2.getRefData().getPosition(); - float d = Ogre::Vector3(actor1Pos.pos).distance(Ogre::Vector3(actor2Pos.pos)); - if (againstPlayer || actor2.getClass().getCreatureStats(actor2).getAiSequence().canAddTarget(actor2Pos, d)) - { - bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2); + bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2); - if (againstPlayer) LOS &= MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1); + if (againstPlayer) LOS &= MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1); - if (LOS) + if (LOS) + { + MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); + if (!againstPlayer) // start combat between each other { - MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); - if (!againstPlayer) // start combat between each other - { - MWBase::Environment::get().getMechanicsManager()->startCombat(actor2, actor1); - } + MWBase::Environment::get().getMechanicsManager()->startCombat(actor2, actor1); } } } @@ -964,19 +983,10 @@ namespace MWMechanics } } - static Ogre::Vector3 sBasePoint; - bool comparePtrDist (const MWWorld::Ptr& ptr1, const MWWorld::Ptr& ptr2) - { - return (sBasePoint.squaredDistance(Ogre::Vector3(ptr1.getRefData().getPosition().pos)) - < sBasePoint.squaredDistance(Ogre::Vector3(ptr2.getRefData().getPosition().pos))); - } - void Actors::update (float duration, bool paused) { if(!paused) - { - std::list listGuards; // at the moment only guards certainly will fight with creatures - + { static float timerUpdateAITargets = 0; // target lists get updated once every 1.0 sec @@ -989,16 +999,10 @@ namespace MWMechanics // Note, the new hit object for this frame may be set by CharacterController::update -> Animation::runAnimation // (below) iter->first.getClass().getCreatureStats(iter->first).setLastHitObject(std::string()); - - // add guards to list to later make them fight with creatures - if (timerUpdateAITargets == 0 && iter->first.getClass().isClass(iter->first, "Guard")) - listGuards.push_back(iter->first); } MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - listGuards.push_back(player); - // AI and magic effects update for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { @@ -1008,20 +1012,16 @@ namespace MWMechanics if (MWBase::Environment::get().getMechanicsManager()->isAIActive()) { - // make guards and creatures fight each other - if (timerUpdateAITargets == 0 && iter->first.getTypeName() == typeid(ESM::Creature).name() && !listGuards.empty()) - { - sBasePoint = Ogre::Vector3(iter->first.getRefData().getPosition().pos); - listGuards.sort(comparePtrDist); // try to engage combat starting from the nearest guard - - for (std::list::iterator it = listGuards.begin(); it != listGuards.end(); ++it) + if (timerUpdateAITargets == 0) + { + for(PtrControllerMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { - engageCombat(iter->first, *it, *it == player); + if (it->first == iter->first || iter->first == player) // player is not AI-controlled + continue; + engageCombat(iter->first, it->first, it->first == player); } } - if (iter->first != player) engageCombat(iter->first, player, true); - if (iter->first.getClass().isNpc() && iter->first != player) updateCrimePersuit(iter->first, duration); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index c0a97a1b1..491954495 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -174,6 +174,8 @@ namespace MWMechanics return true; MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + if (target.isEmpty()) + return false; if(target.getClass().getCreatureStats(target).isDead()) return true; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 5ab7e1730..c2bb0f2f4 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -121,3 +121,8 @@ MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) { } + +MWWorld::Ptr MWMechanics::AiFollow::getTarget() const +{ + return MWBase::Environment::get().getWorld()->searchPtr(mActorId, false); +} diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index e9587b36e..744a9c505 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -31,6 +31,8 @@ namespace MWMechanics AiFollow(const ESM::AiSequence::AiFollow* follow); + MWWorld::Ptr getTarget() const; + virtual AiFollow *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 02f00dfc6..1d0065679 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -72,6 +72,16 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const return true; } +std::list::const_iterator AiSequence::begin() const +{ + return mPackages.begin(); +} + +std::list::const_iterator AiSequence::end() const +{ + return mPackages.end(); +} + bool AiSequence::isInCombat() const { for(std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index bc20dc61b..af14cce59 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -50,6 +50,10 @@ namespace MWMechanics virtual ~AiSequence(); + /// Iterator may be invalidated by any function calls other than begin() or end(). + std::list::const_iterator begin() const; + std::list::const_iterator end() const; + /// Returns currently executing AiPackage type /** \see enum AiPackage::TypeId **/ int getTypeId() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 861d5e110..fd4269930 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1171,6 +1171,8 @@ namespace MWMechanics void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) { + if (ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(target)) + return; ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr); if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) { diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index c3f94d7f1..e880030f9 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -72,12 +72,6 @@ namespace MWWorld public: - /// NPC-stances. - enum Stance - { - Run, Sneak - }; - virtual ~Class(); const std::string& getTypeName() const { From 6262d6c9644b5459078126b389dc218db8eb680a Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 27 Jul 2014 23:10:58 +0200 Subject: [PATCH 14/17] Don't leave stale player CharacterController in Actors when loading game (Fixes #1713) --- apps/openmw/mwmechanics/actors.cpp | 14 ++++++++------ apps/openmw/mwworld/worldimp.cpp | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 36397ac09..a215a71a3 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -924,12 +924,7 @@ namespace MWMechanics Actors::~Actors() { - PtrControllerMap::iterator it(mActors.begin()); - for (; it != mActors.end(); ++it) - { - delete it->second; - it->second = NULL; - } + clear(); } void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately) @@ -1359,6 +1354,13 @@ namespace MWMechanics void Actors::clear() { + PtrControllerMap::iterator it(mActors.begin()); + for (; it != mActors.end(); ++it) + { + delete it->second; + it->second = NULL; + } + mActors.clear(); mDeathCount.clear(); } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 10c2e4974..14746526b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2111,8 +2111,8 @@ namespace MWWorld void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable) { OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle()); - - physicActor->enableCollisionBody(enable); + if (physicActor) + physicActor->enableCollisionBody(enable); } bool World::findInteriorPosition(const std::string &name, ESM::Position &pos) From 45206bc3f6c973d899f70e367921206bf1a53715 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 00:32:59 +0200 Subject: [PATCH 15/17] Savegame: write and read dynamic Store before Cells --- apps/openmw/mwworld/worldimp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 14746526b..ebbc16c23 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -319,8 +319,9 @@ namespace MWWorld MWMechanics::CreatureStats::writeActorIdCounter(writer); progress.increaseProgress(); + mStore.write (writer, progress); // dynamic Store must be written (and read) before Cells, so that + // references to custom made records will be recognized mCells.write (writer, progress); - mStore.write (writer, progress); mGlobalVariables.write (writer, progress); mPlayer->write (writer, progress); mWeatherManager->write (writer, progress); From 315b022d2d01a1887cd5a76f108b14a2d160b103 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 00:55:57 +0200 Subject: [PATCH 16/17] Add transfer gold from all services to NPC trade gold pool --- apps/openmw/mwgui/merchantrepair.cpp | 6 ++++++ apps/openmw/mwgui/spellbuyingwindow.cpp | 5 +++++ apps/openmw/mwgui/spellcreationdialog.cpp | 8 +++++++- apps/openmw/mwgui/trainingwindow.cpp | 3 +++ apps/openmw/mwgui/travelwindow.cpp | 6 ++++++ apps/openmw/mwmechanics/enchanting.cpp | 4 ++++ 6 files changed, 31 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 013a40017..b6f9936a8 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -10,6 +10,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" @@ -134,6 +136,10 @@ void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& actorStats = mActor.getClass().getCreatureStats(mActor); + actorStats.setGoldPool(actorStats.getGoldPool() + price); + startRepair(mActor); } diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index d6c716453..cd378aadf 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -135,6 +135,11 @@ namespace MWGui MWMechanics::Spells& spells = stats.getSpells(); spells.add (mSpellsWidgetMap.find(_sender)->second); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); + npcStats.setGoldPool(npcStats.getGoldPool() + price); + startSpellBuying(mPtr); MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index f1ca3c802..63cbdb567 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -352,7 +352,13 @@ namespace MWGui mSpell.mName = mNameEdit->getCaption(); - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, boost::lexical_cast(mPriceLabel->getCaption()), player); + int price = boost::lexical_cast(mPriceLabel->getCaption()); + + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); + npcStats.setGoldPool(npcStats.getGoldPool() + price); MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 6463db3d7..babfe099f 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -159,6 +159,9 @@ namespace MWGui // remove gold player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + // add gold to NPC trading gold pool + npcStats.setGoldPool(npcStats.getGoldPool() + price); + // go back to game mode MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index d874e8c0c..ae191e764 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -12,6 +12,8 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/actionteleport.hpp" @@ -150,6 +152,10 @@ namespace MWGui player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); + npcStats.setGoldPool(npcStats.getGoldPool() + price); + MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(1); ESM::Position pos = *_sender->getUserData(); std::string cellname = _sender->getUserString("Destination"); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 18732514b..9663a2d51 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -292,5 +292,9 @@ namespace MWMechanics MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice(), player); + + // add gold to NPC trading gold pool + CreatureStats& enchanterStats = mEnchanter.getClass().getCreatureStats(mEnchanter); + enchanterStats.setGoldPool(enchanterStats.getGoldPool() + getEnchantPrice()); } } From c6d3b0b70b27d7bf6c590dbaa08ae1e60e1d38fc Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 02:27:48 +0200 Subject: [PATCH 17/17] Moved merchant restock from trade start to dialogue start, since other services also interact with it. --- apps/openmw/mwgui/dialogue.cpp | 25 +++++++++++++++++++++++++ apps/openmw/mwgui/dialogue.hpp | 1 + apps/openmw/mwgui/tradewindow.cpp | 25 ------------------------- apps/openmw/mwgui/tradewindow.hpp | 2 -- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 2283e0cbe..23098bf42 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -396,6 +396,31 @@ namespace MWGui mLinks.clear(); updateOptions(); + + restock(); + } + + void DialogueWindow::restock() + { + MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); + float delay = MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->getFloat(); + + if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getLastRestockTime() + delay) + { + sellerStats.setGoldPool(mPtr.getClass().getBaseGold(mPtr)); + + mPtr.getClass().restock(mPtr); + + // Also restock any containers owned by this merchant, which are also available to buy in the trade window + std::vector itemSources; + MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources); + for (std::vector::iterator it = itemSources.begin(); it != itemSources.end(); ++it) + { + it->getClass().restock(*it); + } + + sellerStats.setLastRestockTime(MWBase::Environment::get().getWorld()->getTimeStamp()); + } } void DialogueWindow::setKeywords(std::list keyWords) diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 516c04942..71935dfaf 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -152,6 +152,7 @@ namespace MWGui private: void updateOptions(); + void restock(); int mServices; diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index c56c2ee94..f94e39b2f 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -99,8 +99,6 @@ namespace MWGui mCurrentBalance = 0; mCurrentMerchantOffer = 0; - restock(); - std::vector itemSources; MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources); @@ -509,29 +507,6 @@ namespace MWGui return merchantGold; } - void TradeWindow::restock() - { - MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); - float delay = MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->getFloat(); - - if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getLastRestockTime() + delay) - { - sellerStats.setGoldPool(mPtr.getClass().getBaseGold(mPtr)); - - mPtr.getClass().restock(mPtr); - - // Also restock any containers owned by this merchant, which are also available to buy in the trade window - std::vector itemSources; - MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources); - for (std::vector::iterator it = itemSources.begin(); it != itemSources.end(); ++it) - { - it->getClass().restock(*it); - } - - sellerStats.setLastRestockTime(MWBase::Environment::get().getWorld()->getTimeStamp()); - } - } - void TradeWindow::resetReference() { ReferenceInterface::resetReference(); diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index b487a8870..7baf0ce8e 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -104,8 +104,6 @@ namespace MWGui virtual void onReferenceUnavailable(); int getMerchantGold(); - - void restock(); }; }