diff --git a/CHANGELOG.md b/CHANGELOG.md index ccae467f8..3b3e9cb21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,7 @@ 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 + Bug #5038: Enchanting success chance calculations are blatantly wrong Feature #1774: Handle AvoidNode Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 7768aa945..caeb65575 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -107,20 +107,11 @@ namespace MWGui void EnchantingDialog::updateLabels() { - std::stringstream enchantCost; - enchantCost << std::setprecision(1) << std::fixed << mEnchanting.getEnchantPoints(); - mEnchantmentPoints->setCaption(enchantCost.str() + " / " + MyGUI::utility::toString(mEnchanting.getMaxEnchantValue())); - - mCharge->setCaption(MyGUI::utility::toString(mEnchanting.getGemCharge())); - - int successChance = int(mEnchanting.getEnchantChance()); - mSuccessChance->setCaption(MyGUI::utility::toString(std::max(0, successChance))); - - std::stringstream castCost; - castCost << mEnchanting.getEffectiveCastCost(); - mCastCost->setCaption(castCost.str()); - - mPrice->setCaption(MyGUI::utility::toString(mEnchanting.getEnchantPrice())); + mEnchantmentPoints->setCaption(std::to_string(static_cast(mEnchanting.getEnchantPoints(false))) + " / " + std::to_string(mEnchanting.getMaxEnchantValue())); + mCharge->setCaption(std::to_string(mEnchanting.getGemCharge())); + mSuccessChance->setCaption(std::to_string(std::max(0, std::min(100, mEnchanting.getEnchantChance())))); + mCastCost->setCaption(std::to_string(mEnchanting.getEffectiveCastCost())); + mPrice->setCaption(std::to_string(mEnchanting.getEnchantPrice())); switch(mEnchanting.getCastStyle()) { @@ -322,7 +313,7 @@ namespace MWGui return; } - if (mEnchanting.getEnchantPoints() > mEnchanting.getMaxEnchantValue()) + if (static_cast(mEnchanting.getEnchantPoints(false)) > mEnchanting.getMaxEnchantValue()) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage29}"); return; diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 20121c2d9..0e7fa5ec0 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -164,46 +164,36 @@ namespace MWMechanics * * Formula on UESPWiki is not entirely correct. */ - int Enchanting::getEnchantPoints() const + float Enchanting::getEnchantPoints(bool precise) const { if (mEffectList.mList.empty()) // No effects added, cost = 0 return 0; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - std::vector mEffects = mEffectList.mList; + const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); + const float fEnchantmentConstantDurationMult = store.get().find("fEnchantmentConstantDurationMult")->mValue.getFloat(); - int enchantmentCost = 0; - float cost = 0; - for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) + float enchantmentCost = 0.f; + float cost = 0.f; + for (const ESM::ENAMstruct& effect : mEffectList.mList) { - float baseCost = (store.get().find(it->mEffectID))->mData.mBaseCost; - int magMin = std::max(1, it->mMagnMin); - int magMax = std::max(1, it->mMagnMax); - int area = std::max(1, it->mArea); - - float magnitudeCost = (magMin + magMax) * baseCost * 0.05f; + float baseCost = (store.get().find(effect.mEffectID))->mData.mBaseCost; + int magMin = std::max(1, effect.mMagnMin); + int magMax = std::max(1, effect.mMagnMax); + int area = std::max(1, effect.mArea); + float duration = static_cast(effect.mDuration); if (mCastStyle == ESM::Enchantment::ConstantEffect) - { - magnitudeCost *= store.get().find("fEnchantmentConstantDurationMult")->mValue.getFloat(); - } - else - { - magnitudeCost *= it->mDuration; - } + duration = fEnchantmentConstantDurationMult; - float areaCost = area * 0.05f * baseCost; - - const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); - - cost += (magnitudeCost + areaCost) * fEffectCostMult; + cost += ((magMin + magMax) * duration + area) * baseCost * fEffectCostMult * 0.05f; cost = std::max(1.f, cost); - if (it->mRange == ESM::RT_Target) - cost *= 1.5; + if (effect.mRange == ESM::RT_Target) + cost *= 1.5f; - enchantmentCost += static_cast(cost); + enchantmentCost += precise ? cost : std::floor(cost); } return enchantmentCost; @@ -215,7 +205,7 @@ namespace MWMechanics if (mCastStyle == ESM::Enchantment::ConstantEffect) return 0; - return getEnchantPoints(); + return static_cast(getEnchantPoints(false)); } int Enchanting::getEffectiveCastCost() const @@ -279,21 +269,21 @@ namespace MWMechanics mEnchanter = enchanter; } - float Enchanting::getEnchantChance() const + int Enchanting::getEnchantChance() const { const CreatureStats& stats = mEnchanter.getClass().getCreatureStats(mEnchanter); - - float chance1 = (mEnchanter.getClass().getSkill(mEnchanter, ESM::Skill::Enchant) + - (0.25f * stats.getAttribute (ESM::Attribute::Intelligence).getModified()) - + (0.125f * stats.getAttribute (ESM::Attribute::Luck).getModified())); - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const float a = static_cast(mEnchanter.getClass().getSkill(mEnchanter, ESM::Skill::Enchant)); + const float b = static_cast(stats.getAttribute (ESM::Attribute::Intelligence).getModified()); + const float c = static_cast(stats.getAttribute (ESM::Attribute::Luck).getModified()); + const float fEnchantmentChanceMult = gmst.find("fEnchantmentChanceMult")->mValue.getFloat(); + const float fEnchantmentConstantChanceMult = gmst.find("fEnchantmentConstantChanceMult")->mValue.getFloat(); - float chance2 = 7.5f / (gmst.find("fEnchantmentChanceMult")->mValue.getFloat() * ((mCastStyle == ESM::Enchantment::ConstantEffect) ? - gmst.find("fEnchantmentConstantChanceMult")->mValue.getFloat() : 1.0f )) - * getEnchantPoints(); + float x = (a - getEnchantPoints()*fEnchantmentChanceMult + 0.2f * b + 0.1f * c) * stats.getFatigueTerm(); + if (mCastStyle == ESM::Enchantment::ConstantEffect) + x *= fEnchantmentConstantChanceMult; - return (chance1-chance2); + return static_cast(x); } void Enchanting::payForEnchantment() const diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 275147671..6f72d96a8 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -37,13 +37,13 @@ namespace MWMechanics bool create(); //Return true if created, false if failed. void nextCastStyle(); //Set enchant type to next possible type (for mOldItemPtr object) int getCastStyle() const; - int getEnchantPoints() const; + float getEnchantPoints(bool precise = true) const; int getBaseCastCost() const; // To be saved in the enchantment's record int getEffectiveCastCost() const; // Effective cost taking player Enchant skill into account, used for preview purposes in the UI int getEnchantPrice() const; int getMaxEnchantValue() const; int getGemCharge() const; - float getEnchantChance() const; + int getEnchantChance() const; bool soulEmpty() const; //Return true if empty bool itemEmpty() const; //Return true if empty void payForEnchantment() const;