Allow to enchant multiple projectiles at once (feature #3517)

pull/2646/head
Andrei Kortunov 5 years ago
parent 119382ed13
commit 71e1d576cd

@ -189,6 +189,7 @@
Feature #2229: Improve pathfinding AI Feature #2229: Improve pathfinding AI
Feature #3025: Analogue gamepad movement controls Feature #3025: Analogue gamepad movement controls
Feature #3442: Default values for fallbacks from ini file Feature #3442: Default values for fallbacks from ini file
Feature #3517: Multiple projectiles enchantment
Feature #3610: Option to invert X axis Feature #3610: Option to invert X axis
Feature #3871: Editor: Terrain Selection Feature #3871: Editor: Terrain Selection
Feature #3893: Implicit target for "set" function in console Feature #3893: Implicit target for "set" function in console

@ -1,6 +1,7 @@
#include "enchanting.hpp" #include "enchanting.hpp"
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/settings/settings.hpp>
#include "../mwworld/manualref.hpp" #include "../mwworld/manualref.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
@ -62,7 +63,6 @@ namespace MWMechanics
const MWWorld::Ptr& player = getPlayer(); const MWWorld::Ptr& player = getPlayer();
MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player);
ESM::Enchantment enchantment; ESM::Enchantment enchantment;
enchantment.mData.mCharge = getGemCharge();
enchantment.mData.mAutocalc = 0; enchantment.mData.mAutocalc = 0;
enchantment.mData.mType = mCastStyle; enchantment.mData.mType = mCastStyle;
enchantment.mData.mCost = getBaseCastCost(); enchantment.mData.mCost = getBaseCastCost();
@ -81,19 +81,26 @@ namespace MWMechanics
mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2);
} }
if(mCastStyle==ESM::Enchantment::ConstantEffect)
{
enchantment.mData.mCharge=0;
}
enchantment.mEffects = mEffectList; 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 // Apply the enchantment
const ESM::Enchantment *enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment);
std::string newItemId = mOldItemPtr.getClass().applyEnchantment(mOldItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName); std::string newItemId = mOldItemPtr.getClass().applyEnchantment(mOldItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName);
// Add the new item to player inventory and remove the old one // Add the new item to player inventory and remove the old one
store.remove(mOldItemPtr, 1, player); store.remove(mOldItemPtr, count, player);
store.add(newItemId, 1, player); store.add(newItemId, count, player);
if(!mSelfEnchanting) if(!mSelfEnchanting)
payForEnchantment(); payForEnchantment();
@ -123,20 +130,22 @@ namespace MWMechanics
} }
else if (mWeaponType != -1) else if (mWeaponType != -1)
{ // Weapon { // Weapon
ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(mWeaponType)->mWeaponClass;
switch(mCastStyle) switch(mCastStyle)
{ {
case ESM::Enchantment::WhenStrikes: case ESM::Enchantment::WhenStrikes:
mCastStyle = ESM::Enchantment::WhenUsed; if (weapclass == ESM::WeaponType::Melee || weapclass == ESM::WeaponType::Ranged)
mCastStyle = ESM::Enchantment::WhenUsed;
return; return;
case ESM::Enchantment::WhenUsed: case ESM::Enchantment::WhenUsed:
if (powerfulSoul) if (powerfulSoul && weapclass != ESM::WeaponType::Ammo && weapclass != ESM::WeaponType::Thrown)
mCastStyle = ESM::Enchantment::ConstantEffect; mCastStyle = ESM::Enchantment::ConstantEffect;
else if (getWeaponType(mWeaponType)->mWeaponClass != ESM::WeaponType::Ranged) else if (weapclass != ESM::WeaponType::Ranged)
mCastStyle = ESM::Enchantment::WhenStrikes; mCastStyle = ESM::Enchantment::WhenStrikes;
return; return;
default: // takes care of Constant effect too default: // takes care of Constant effect too
mCastStyle = ESM::Enchantment::WhenUsed; mCastStyle = ESM::Enchantment::WhenUsed;
if (getWeaponType(mWeaponType)->mWeaponClass != ESM::WeaponType::Ranged) if (weapclass != ESM::WeaponType::Ranged)
mCastStyle = ESM::Enchantment::WhenStrikes; mCastStyle = ESM::Enchantment::WhenStrikes;
return; return;
} }
@ -200,6 +209,53 @@ namespace MWMechanics
return enchantmentCost; return enchantmentCost;
} }
const ESM::Enchantment* Enchanting::getRecord(const ESM::Enchantment& toFind) const
{
const MWWorld::Store<ESM::Enchantment>& enchantments = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>();
MWWorld::Store<ESM::Enchantment>::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<static_cast<int> (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 int Enchanting::getBaseCastCost() const
{ {
@ -224,6 +280,7 @@ namespace MWMechanics
float priceMultipler = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("fEnchantmentValueMult")->mValue.getFloat(); float priceMultipler = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("fEnchantmentValueMult")->mValue.getFloat();
int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mEnchanter, static_cast<int>(getEnchantPoints() * priceMultipler), true); int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mEnchanter, static_cast<int>(getEnchantPoints() * priceMultipler), true);
price *= getEnchantItemsCount() * getTypeMultiplier();
return price; return price;
} }
@ -282,13 +339,45 @@ namespace MWMechanics
const float fEnchantmentChanceMult = gmst.find("fEnchantmentChanceMult")->mValue.getFloat(); const float fEnchantmentChanceMult = gmst.find("fEnchantmentChanceMult")->mValue.getFloat();
const float fEnchantmentConstantChanceMult = gmst.find("fEnchantmentConstantChanceMult")->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) if (mCastStyle == ESM::Enchantment::ConstantEffect)
x *= fEnchantmentConstantChanceMult; x *= fEnchantmentConstantChanceMult;
return static_cast<int>(x); return static_cast<int>(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 void Enchanting::payForEnchantment() const
{ {
const MWWorld::Ptr& player = getPlayer(); const MWWorld::Ptr& player = getPlayer();

@ -4,6 +4,7 @@
#include <string> #include <string>
#include <components/esm/effectlist.hpp> #include <components/esm/effectlist.hpp>
#include <components/esm/loadench.hpp>
#include "../mwworld/ptr.hpp" #include "../mwworld/ptr.hpp"
@ -25,6 +26,8 @@ namespace MWMechanics
std::string mObjectType; std::string mObjectType;
int mWeaponType; int mWeaponType;
const ESM::Enchantment* getRecord(const ESM::Enchantment& newEnchantment) const;
public: public:
Enchanting(); Enchanting();
void setEnchanter(const MWWorld::Ptr& enchanter); void setEnchanter(const MWWorld::Ptr& enchanter);
@ -45,6 +48,8 @@ namespace MWMechanics
int getMaxEnchantValue() const; int getMaxEnchantValue() const;
int getGemCharge() const; int getGemCharge() const;
int getEnchantChance() const; int getEnchantChance() const;
int getEnchantItemsCount() const;
float getTypeMultiplier() const;
bool soulEmpty() const; //Return true if empty bool soulEmpty() const; //Return true if empty
bool itemEmpty() const; //Return true if empty bool itemEmpty() const; //Return true if empty
void payForEnchantment() const; void payForEnchantment() const;

@ -106,6 +106,11 @@ namespace MWWorld
return iter; return iter;
} }
SharedIterator &operator+=(int advance) {
mIter += advance;
return *this;
}
SharedIterator &operator--() { SharedIterator &operator--() {
--mIter; --mIter;
return *this; return *this;

@ -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 makes the movement speed behavior more fair between different races.
This setting can be controlled in Advanced tab of the launcher. 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.

@ -267,6 +267,11 @@ use magic item animations = false
# Don't use race weight in NPC movement speed calculations # Don't use race weight in NPC movement speed calculations
normalise race speed = false 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] [General]
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).

Loading…
Cancel
Save