diff --git a/CHANGELOG.md b/CHANGELOG.md index aadd6efd0a..37f45037b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ Bug #5001: Possible data race in the Animation::setAlpha() Bug #5004: Werewolves shield their eyes during storm Bug #5018: Spell tooltips don't support purely negative magnitudes + Bug #5028: Offered price caps are not trading-specific Feature #1774: Handle AvoidNode Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index b197ecef65..15a43d55fa 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -468,16 +468,26 @@ namespace MWGui int merchantOffer = 0; + // The offered price must be capped at 75% of the base price to avoid exploits + // connected to buying and selling the same item. + // This value has been determined by researching the limitations of the vanilla formula + // and may not be sufficient if getBarterOffer behavior has been changed. std::vector playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); for (const ItemStack& itemStack : playerBorrowed) { - merchantOffer -= MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(itemStack.mBase, itemStack.mCount), true); + const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); + const int cap = static_cast(std::max(1.f, 0.75f * basePrice)); // Minimum buying price -- 75% of the base + const int buyingPrice = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, basePrice, true); + merchantOffer -= std::max(cap, buyingPrice); } std::vector merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); for (const ItemStack& itemStack : merchantBorrowed) { - merchantOffer += MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(itemStack.mBase, itemStack.mCount), false); + const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); + const int cap = static_cast(std::min(1.f, 0.75f * basePrice)); // Maximum selling price -- 75% of the base + const int sellingPrice = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, basePrice, false); + merchantOffer += std::min(cap, sellingPrice); } int diff = merchantOffer - mCurrentMerchantOffer; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index c98b442522..234c39aaa4 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -676,8 +676,8 @@ namespace MWMechanics float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); float pcTerm = (clampedDisposition - 50 + a + b + c) * playerStats.getFatigueTerm(); float npcTerm = (d + e + f) * sellerStats.getFatigueTerm(); - float buyTerm = 0.01f * std::max(75.f, (100 - 0.5f * (pcTerm - npcTerm))); - float sellTerm = 0.01f * std::min(75.f, (50 - 0.5f * (npcTerm - pcTerm))); + float buyTerm = 0.01f * (100 - 0.5f * (pcTerm - npcTerm)); + float sellTerm = 0.01f * (50 - 0.5f * (npcTerm - pcTerm)); int offerPrice = int(basePrice * (buying ? buyTerm : sellTerm)); return std::max(1, offerPrice); }