From 71e1d576cdb10dc36e2c49935c6803756bfe06ed Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 27 Dec 2019 11:21:19 +0400 Subject: [PATCH] Allow to enchant multiple projectiles at once (feature #3517) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/enchanting.cpp | 115 ++++++++++++++++-- apps/openmw/mwmechanics/enchanting.hpp | 5 + apps/openmw/mwworld/store.hpp | 5 + .../reference/modding/settings/game.rst | 16 +++ files/settings-default.cfg | 5 + 6 files changed, 134 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b50fdff6b..67a2632b93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -189,6 +189,7 @@ Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls Feature #3442: Default values for fallbacks from ini file + Feature #3517: Multiple projectiles enchantment Feature #3610: Option to invert X axis Feature #3871: Editor: Terrain Selection Feature #3893: Implicit target for "set" function in console diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 0545608519..9c4290820e 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -1,6 +1,7 @@ #include "enchanting.hpp" #include +#include #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" @@ -62,7 +63,6 @@ namespace MWMechanics const MWWorld::Ptr& player = getPlayer(); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); ESM::Enchantment enchantment; - enchantment.mData.mCharge = getGemCharge(); enchantment.mData.mAutocalc = 0; enchantment.mData.mType = mCastStyle; enchantment.mData.mCost = getBaseCastCost(); @@ -81,19 +81,26 @@ namespace MWMechanics mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); } - if(mCastStyle==ESM::Enchantment::ConstantEffect) - { - enchantment.mData.mCharge=0; - } enchantment.mEffects = mEffectList; + int count = getEnchantItemsCount(); + + if(mCastStyle==ESM::Enchantment::ConstantEffect) + enchantment.mData.mCharge = 0; + else + enchantment.mData.mCharge = getGemCharge() / count; + + // Try to find a dynamic enchantment with the same stats, create a new one if not found. + const ESM::Enchantment* enchantmentPtr = getRecord(enchantment); + if (enchantmentPtr == nullptr) + enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment); + // Apply the enchantment - const ESM::Enchantment *enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment); std::string newItemId = mOldItemPtr.getClass().applyEnchantment(mOldItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName); // Add the new item to player inventory and remove the old one - store.remove(mOldItemPtr, 1, player); - store.add(newItemId, 1, player); + store.remove(mOldItemPtr, count, player); + store.add(newItemId, count, player); if(!mSelfEnchanting) payForEnchantment(); @@ -123,20 +130,22 @@ namespace MWMechanics } else if (mWeaponType != -1) { // Weapon + ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(mWeaponType)->mWeaponClass; switch(mCastStyle) { case ESM::Enchantment::WhenStrikes: - mCastStyle = ESM::Enchantment::WhenUsed; + if (weapclass == ESM::WeaponType::Melee || weapclass == ESM::WeaponType::Ranged) + mCastStyle = ESM::Enchantment::WhenUsed; return; case ESM::Enchantment::WhenUsed: - if (powerfulSoul) + if (powerfulSoul && weapclass != ESM::WeaponType::Ammo && weapclass != ESM::WeaponType::Thrown) mCastStyle = ESM::Enchantment::ConstantEffect; - else if (getWeaponType(mWeaponType)->mWeaponClass != ESM::WeaponType::Ranged) + else if (weapclass != ESM::WeaponType::Ranged) mCastStyle = ESM::Enchantment::WhenStrikes; return; default: // takes care of Constant effect too mCastStyle = ESM::Enchantment::WhenUsed; - if (getWeaponType(mWeaponType)->mWeaponClass != ESM::WeaponType::Ranged) + if (weapclass != ESM::WeaponType::Ranged) mCastStyle = ESM::Enchantment::WhenStrikes; return; } @@ -200,6 +209,53 @@ namespace MWMechanics return enchantmentCost; } + const ESM::Enchantment* Enchanting::getRecord(const ESM::Enchantment& toFind) const + { + const MWWorld::Store& enchantments = MWBase::Environment::get().getWorld()->getStore().get(); + MWWorld::Store::iterator iter (enchantments.begin()); + iter += (enchantments.getSize() - enchantments.getDynamicSize()); + for (; iter != enchantments.end(); ++iter) + { + if (iter->mEffects.mList.size() != toFind.mEffects.mList.size()) + continue; + + if (iter->mData.mAutocalc != toFind.mData.mAutocalc + || iter->mData.mType != toFind.mData.mType + || iter->mData.mCost != toFind.mData.mCost + || iter->mData.mCharge != toFind.mData.mCharge) + continue; + + // Don't choose an ID that came from the content files, would have unintended side effects + if (!enchantments.isDynamic(iter->mId)) + continue; + + bool mismatch = false; + + for (int i=0; i (iter->mEffects.mList.size()); ++i) + { + const ESM::ENAMstruct& first = iter->mEffects.mList[i]; + const ESM::ENAMstruct& second = toFind.mEffects.mList[i]; + + if (first.mEffectID!=second.mEffectID || + first.mArea!=second.mArea || + first.mRange!=second.mRange || + first.mSkill!=second.mSkill || + first.mAttribute!=second.mAttribute || + first.mMagnMin!=second.mMagnMin || + first.mMagnMax!=second.mMagnMax || + first.mDuration!=second.mDuration) + { + mismatch = true; + break; + } + } + + if (!mismatch) + return &(*iter); + } + + return nullptr; + } int Enchanting::getBaseCastCost() const { @@ -224,6 +280,7 @@ namespace MWMechanics float priceMultipler = MWBase::Environment::get().getWorld()->getStore().get().find ("fEnchantmentValueMult")->mValue.getFloat(); int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mEnchanter, static_cast(getEnchantPoints() * priceMultipler), true); + price *= getEnchantItemsCount() * getTypeMultiplier(); return price; } @@ -282,13 +339,45 @@ namespace MWMechanics const float fEnchantmentChanceMult = gmst.find("fEnchantmentChanceMult")->mValue.getFloat(); const float fEnchantmentConstantChanceMult = gmst.find("fEnchantmentConstantChanceMult")->mValue.getFloat(); - float x = (a - getEnchantPoints()*fEnchantmentChanceMult + 0.2f * b + 0.1f * c) * stats.getFatigueTerm(); + float x = (a - getEnchantPoints() * fEnchantmentChanceMult * getTypeMultiplier() * getEnchantItemsCount() + 0.2f * b + 0.1f * c) * stats.getFatigueTerm(); if (mCastStyle == ESM::Enchantment::ConstantEffect) x *= fEnchantmentConstantChanceMult; return static_cast(x); } + int Enchanting::getEnchantItemsCount() const + { + int count = 1; + float enchantPoints = getEnchantPoints(); + if (mWeaponType != -1 && enchantPoints > 0) + { + ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(mWeaponType)->mWeaponClass; + if (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo) + { + static const float multiplier = std::max(0.f, std::min(1.0f, Settings::Manager::getFloat("projectiles enchant multiplier", "Game"))); + MWWorld::Ptr player = getPlayer(); + int itemsInInventoryCount = player.getClass().getContainerStore(player).count(mOldItemPtr.getCellRef().getRefId()); + count = std::min(itemsInInventoryCount, std::max(1, int(getGemCharge() * multiplier / enchantPoints))); + } + } + + return count; + } + + float Enchanting::getTypeMultiplier() const + { + static const bool useMultiplier = Settings::Manager::getFloat("projectiles enchant multiplier", "Game") > 0; + if (useMultiplier && mWeaponType != -1 && getEnchantPoints() > 0) + { + ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(mWeaponType)->mWeaponClass; + if (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo) + return 0.125f; + } + + return 1.f; + } + void Enchanting::payForEnchantment() const { const MWWorld::Ptr& player = getPlayer(); diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 6996fa24d2..33a2820938 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -4,6 +4,7 @@ #include #include +#include #include "../mwworld/ptr.hpp" @@ -25,6 +26,8 @@ namespace MWMechanics std::string mObjectType; int mWeaponType; + const ESM::Enchantment* getRecord(const ESM::Enchantment& newEnchantment) const; + public: Enchanting(); void setEnchanter(const MWWorld::Ptr& enchanter); @@ -45,6 +48,8 @@ namespace MWMechanics int getMaxEnchantValue() const; int getGemCharge() const; int getEnchantChance() const; + int getEnchantItemsCount() const; + float getTypeMultiplier() const; bool soulEmpty() const; //Return true if empty bool itemEmpty() const; //Return true if empty void payForEnchantment() const; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index e7f2d35db0..64c01589e3 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -106,6 +106,11 @@ namespace MWWorld return iter; } + SharedIterator &operator+=(int advance) { + mIter += advance; + return *this; + } + SharedIterator &operator--() { --mIter; return *this; diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 20e0411303..162cb0e2ff 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -282,3 +282,19 @@ equivalent to the one introduced by the equivalent Morrowind Code Patch feature. This makes the movement speed behavior more fair between different races. This setting can be controlled in Advanced tab of the launcher. + +projectiles enchant multiplier +------------------------------ + +:Type: floating point +:Range: 0.0 to 1.0 +:Default: 0.0 + +The value of this setting determines how many projectiles (thrown weapons, arrows and bolts) you can enchant at once according to the following formula: + +count = (soul gem charge * projectiles enchant multiplier) / enchantment strength + +A value of 0 means that you can only enchant one projectile. +If you want to have Morrowind Code Patch-like count of projectiles being enchanted at once, set this value to 0.25 (i.e. 25% of the charge). + +This setting can only be configured by editing the settings configuration file. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index b85a86afe4..ecc6be72ee 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -267,6 +267,11 @@ use magic item animations = false # Don't use race weight in NPC movement speed calculations normalise race speed = false +# Adjusts the number of projectiles you can enchant at once: +# count = (soul gem charge * projectiles enchant multiplier) / enchantment strength +# A value of 0 means that you can only enchant one projectile. +projectiles enchant multiplier = 0 + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).