1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-15 21:19:57 +00:00
openmw-tes3mp/apps/openmw/mwmechanics/alchemy.cpp

659 lines
20 KiB
C++
Raw Normal View History

2012-09-29 08:02:46 +00:00
#include "alchemy.hpp"
2012-10-18 13:33:27 +00:00
#include <cassert>
#include <cmath>
#include <algorithm>
#include <stdexcept>
2012-10-28 13:07:36 +00:00
#include <map>
2015-04-22 15:58:55 +00:00
#include <components/misc/rng.hpp>
#include <components/esm/loadskil.hpp>
#include <components/esm/loadappa.hpp>
#include <components/esm/loadgmst.hpp>
#include <components/esm/loadmgef.hpp>
[General] Implement RecordDynamic packet, part 1 Spell, potion, enchantment, creature, NPC, armor, book, clothing, miscellaneous and weapon record data can now be sent in a RecordDynamic packet. Additionally, the packets include data related to associated magical effects (for spells, potions and enchantments), data related to default inventory contents (for creatures and NPCs) and data related to body parts affected (for armor and clothing). The server now has associated script functions for setting most of the details of the above, with the main exception being individual creature and NPC stats. Records can either be created entirely from scratch or can use an existing record (set via the baseId variable) as a starting point for their values. In the latter case, only the values that are specifically set override the starting values. Creature and NPC records also have an inventoryBaseId that can be used on top of the baseId to base their inventories on another existing record. The client's RecordHelper class has been heavily expanded to allow for the above mentioned functionality. When players create spells, potions and enchantments as part of regular gameplay, they send RecordDynamic packets that provide the server with the complete details of the records that should be created. When they create enchantments, they also provide the server with armor, book, clothing and weapon records corresponding to the items they've enchanted. This functionality added by this packet was originally supposed to be exclusive to the rewrite, but I've gone ahead and tried to provide it for the pre-rewrite in a way that can mostly be reused for the rewrite.
2018-07-30 07:56:26 +00:00
/*
Start of tes3mp addition
Include additional headers for multiplayer purposes
*/
#include <components/openmw-mp/TimedLog.hpp>
[General] Implement RecordDynamic packet, part 1 Spell, potion, enchantment, creature, NPC, armor, book, clothing, miscellaneous and weapon record data can now be sent in a RecordDynamic packet. Additionally, the packets include data related to associated magical effects (for spells, potions and enchantments), data related to default inventory contents (for creatures and NPCs) and data related to body parts affected (for armor and clothing). The server now has associated script functions for setting most of the details of the above, with the main exception being individual creature and NPC stats. Records can either be created entirely from scratch or can use an existing record (set via the baseId variable) as a starting point for their values. In the latter case, only the values that are specifically set override the starting values. Creature and NPC records also have an inventoryBaseId that can be used on top of the baseId to base their inventories on another existing record. The client's RecordHelper class has been heavily expanded to allow for the above mentioned functionality. When players create spells, potions and enchantments as part of regular gameplay, they send RecordDynamic packets that provide the server with the complete details of the records that should be created. When they create enchantments, they also provide the server with armor, book, clothing and weapon records corresponding to the items they've enchanted. This functionality added by this packet was originally supposed to be exclusive to the rewrite, but I've gone ahead and tried to provide it for the pre-rewrite in a way that can mostly be reused for the rewrite.
2018-07-30 07:56:26 +00:00
#include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp"
#include "../mwmp/LocalPlayer.hpp"
[General] Implement RecordDynamic packet, part 1 Spell, potion, enchantment, creature, NPC, armor, book, clothing, miscellaneous and weapon record data can now be sent in a RecordDynamic packet. Additionally, the packets include data related to associated magical effects (for spells, potions and enchantments), data related to default inventory contents (for creatures and NPCs) and data related to body parts affected (for armor and clothing). The server now has associated script functions for setting most of the details of the above, with the main exception being individual creature and NPC stats. Records can either be created entirely from scratch or can use an existing record (set via the baseId variable) as a starting point for their values. In the latter case, only the values that are specifically set override the starting values. Creature and NPC records also have an inventoryBaseId that can be used on top of the baseId to base their inventories on another existing record. The client's RecordHelper class has been heavily expanded to allow for the above mentioned functionality. When players create spells, potions and enchantments as part of regular gameplay, they send RecordDynamic packets that provide the server with the complete details of the records that should be created. When they create enchantments, they also provide the server with armor, book, clothing and weapon records corresponding to the items they've enchanted. This functionality added by this packet was originally supposed to be exclusive to the rewrite, but I've gone ahead and tried to provide it for the pre-rewrite in a way that can mostly be reused for the rewrite.
2018-07-30 07:56:26 +00:00
#include "../mwmp/Worldstate.hpp"
/*
End of tes3mp addition
*/
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/cellstore.hpp"
#include "magiceffects.hpp"
#include "creaturestats.hpp"
2014-09-26 15:12:48 +00:00
MWMechanics::Alchemy::Alchemy()
: mValue(0)
2018-09-18 10:57:21 +00:00
, mPotionName("")
2014-09-26 15:12:48 +00:00
{
}
std::set<MWMechanics::EffectKey> MWMechanics::Alchemy::listEffects() const
{
2012-10-28 13:07:36 +00:00
std::map<EffectKey, int> effects;
2013-11-10 20:45:27 +00:00
for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter)
{
if (!iter->isEmpty())
{
const MWWorld::LiveCellRef<ESM::Ingredient> *ingredient = iter->get<ESM::Ingredient>();
2013-11-10 20:45:27 +00:00
std::set<EffectKey> seenEffects;
for (int i=0; i<4; ++i)
2012-11-05 12:07:59 +00:00
if (ingredient->mBase->mData.mEffectID[i]!=-1)
2012-10-28 13:07:36 +00:00
{
EffectKey key (
2012-11-05 12:07:59 +00:00
ingredient->mBase->mData.mEffectID[i], ingredient->mBase->mData.mSkills[i]!=-1 ?
ingredient->mBase->mData.mSkills[i] : ingredient->mBase->mData.mAttributes[i]);
2012-10-28 13:07:36 +00:00
if (seenEffects.insert(key).second)
++effects[key];
2012-10-28 13:07:36 +00:00
}
}
}
2013-11-10 20:45:27 +00:00
2012-10-28 13:07:36 +00:00
std::set<EffectKey> effects2;
2013-11-10 20:45:27 +00:00
2012-10-28 13:07:36 +00:00
for (std::map<EffectKey, int>::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter)
if (iter->second>1)
effects2.insert (iter->first);
2013-11-10 20:45:27 +00:00
2012-10-28 13:07:36 +00:00
return effects2;
}
void MWMechanics::Alchemy::applyTools (int flags, float& value) const
{
bool magnitude = !(flags & ESM::MagicEffect::NoMagnitude);
bool duration = !(flags & ESM::MagicEffect::NoDuration);
bool negative = (flags & ESM::MagicEffect::Harmful) != 0;
int tool = negative ? ESM::Apparatus::Alembic : ESM::Apparatus::Retort;
int setup = 0;
2013-11-10 20:45:27 +00:00
if (!mTools[tool].isEmpty() && !mTools[ESM::Apparatus::Calcinator].isEmpty())
setup = 1;
else if (!mTools[tool].isEmpty())
setup = 2;
else if (!mTools[ESM::Apparatus::Calcinator].isEmpty())
setup = 3;
else
return;
2012-11-05 12:07:59 +00:00
float toolQuality = setup==1 || setup==2 ? mTools[tool].get<ESM::Apparatus>()->mBase->mData.mQuality : 0;
float calcinatorQuality = setup==1 || setup==3 ?
2012-11-05 12:07:59 +00:00
mTools[ESM::Apparatus::Calcinator].get<ESM::Apparatus>()->mBase->mData.mQuality : 0;
float quality = 1;
2013-11-10 20:45:27 +00:00
switch (setup)
{
case 1:
2013-11-10 20:45:27 +00:00
quality = negative ? 2 * toolQuality + 3 * calcinatorQuality :
(magnitude && duration ?
2 * toolQuality + calcinatorQuality : 2/3.0f * (toolQuality + calcinatorQuality) + 0.5f);
break;
2013-11-10 20:45:27 +00:00
case 2:
2013-11-10 20:45:27 +00:00
quality = negative ? 1+toolQuality : (magnitude && duration ? toolQuality : toolQuality + 0.5f);
break;
2013-11-10 20:45:27 +00:00
case 3:
2013-11-10 20:45:27 +00:00
quality = magnitude && duration ? calcinatorQuality : calcinatorQuality + 0.5f;
break;
}
if (setup==3 || !negative)
{
value += quality;
}
else
{
if (quality==0)
2013-11-10 20:45:27 +00:00
throw std::runtime_error ("invalid derived alchemy apparatus quality");
value /= quality;
}
}
void MWMechanics::Alchemy::updateEffects()
{
mEffects.clear();
2012-10-18 13:33:27 +00:00
mValue = 0;
if (countIngredients()<2 || mAlchemist.isEmpty() || mTools[ESM::Apparatus::MortarPestle].isEmpty())
return;
// find effects
std::set<EffectKey> effects (listEffects());
// general alchemy factor
2014-10-02 11:54:56 +00:00
float x = getAlchemyFactor();
2012-11-05 12:07:59 +00:00
x *= mTools[ESM::Apparatus::MortarPestle].get<ESM::Apparatus>()->mBase->mData.mQuality;
2018-08-29 15:38:12 +00:00
x *= MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("fPotionStrengthMult")->mValue.getFloat();
2012-10-18 13:33:27 +00:00
// value
mValue = static_cast<int> (
2018-08-29 15:38:12 +00:00
x * MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("iAlchemyMod")->mValue.getFloat());
2012-10-18 13:33:27 +00:00
// build quantified effect list
for (std::set<EffectKey>::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter)
{
const ESM::MagicEffect *magicEffect =
2013-11-10 20:45:27 +00:00
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (iter->mId);
if (magicEffect->mData.mBaseCost<=0)
{
2019-01-20 11:46:19 +00:00
const std::string os = "invalid base cost for magic effect " + std::to_string(iter->mId);
2019-01-07 13:47:39 +00:00
throw std::runtime_error (os);
}
2013-11-10 20:45:27 +00:00
float fPotionT1MagMul =
2018-08-29 15:38:12 +00:00
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("fPotionT1MagMult")->mValue.getFloat();
if (fPotionT1MagMul<=0)
throw std::runtime_error ("invalid gmst: fPotionT1MagMul");
2013-11-10 20:45:27 +00:00
float fPotionT1DurMult =
2018-08-29 15:38:12 +00:00
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("fPotionT1DurMult")->mValue.getFloat();
if (fPotionT1DurMult<=0)
throw std::runtime_error ("invalid gmst: fPotionT1DurMult");
float magnitude = (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) ?
1.0f : (x / fPotionT1MagMul) / magicEffect->mData.mBaseCost;
float duration = (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) ?
1.0f : (x / fPotionT1DurMult) / magicEffect->mData.mBaseCost;
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
applyTools (magicEffect->mData.mFlags, magnitude);
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
applyTools (magicEffect->mData.mFlags, duration);
2013-11-10 20:45:27 +00:00
duration = roundf(duration);
magnitude = roundf(magnitude);
if (magnitude>0 && duration>0)
{
ESM::ENAMstruct effect;
effect.mEffectID = iter->mId;
2013-11-10 20:45:27 +00:00
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;
2013-11-10 20:45:27 +00:00
effect.mRange = 0;
2013-11-10 20:45:27 +00:00
effect.mArea = 0;
effect.mDuration = static_cast<int>(duration);
effect.mMagnMin = effect.mMagnMax = static_cast<int>(magnitude);
mEffects.push_back (effect);
2013-11-10 20:45:27 +00:00
}
}
}
const ESM::Potion *MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) const
{
const MWWorld::Store<ESM::Potion> &potions =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>();
MWWorld::Store<ESM::Potion>::iterator iter = potions.begin();
for (; iter != potions.end(); ++iter)
2012-10-18 13:33:27 +00:00
{
if (iter->mEffects.mList.size() != mEffects.size())
2012-10-18 13:33:27 +00:00
continue;
2013-11-10 20:45:27 +00:00
if (iter->mName != toFind.mName
|| iter->mScript != toFind.mScript
|| iter->mData.mWeight != toFind.mData.mWeight
|| iter->mData.mValue != toFind.mData.mValue
|| iter->mData.mAutoCalc != toFind.mData.mAutoCalc)
continue;
// Don't choose an ID that came from the content files, would have unintended side effects
// where alchemy can be used to produce quest-relevant items
if (!potions.isDynamic(iter->mId))
continue;
2013-11-10 20:45:27 +00:00
bool mismatch = false;
2012-12-02 17:18:59 +00:00
for (int i=0; i<static_cast<int> (iter->mEffects.mList.size()); ++i)
2012-10-18 13:33:27 +00:00
{
const ESM::ENAMstruct& first = iter->mEffects.mList[i];
2012-10-18 13:33:27 +00:00
const ESM::ENAMstruct& second = mEffects[i];
2013-11-10 20:45:27 +00:00
2012-10-18 13:33:27 +00:00
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;
}
}
2013-11-10 20:45:27 +00:00
2012-10-18 13:33:27 +00:00
if (!mismatch)
return &(*iter);
2012-10-18 13:33:27 +00:00
}
2013-11-10 20:45:27 +00:00
2020-11-13 07:39:47 +00:00
return nullptr;
}
void MWMechanics::Alchemy::removeIngredients()
{
for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter)
if (!iter->isEmpty())
{
iter->getContainerStore()->remove(*iter, 1, mAlchemist);
/*
Start of tes3mp addition
Store this item removal for later sending to avoid packet spam
*/
mwmp::Main::get().getLocalPlayer()->storeItemRemoval(iter->getCellRef().getRefId(), 1);
/*
End of tes3mp addition
*/
if (iter->getRefData().getCount()<1)
*iter = MWWorld::Ptr();
}
2013-11-10 20:45:27 +00:00
updateEffects();
}
void MWMechanics::Alchemy::addPotion (const std::string& name)
{
ESM::Potion newRecord;
2013-11-10 20:45:27 +00:00
newRecord.mData.mWeight = 0;
2013-11-10 20:45:27 +00:00
for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter)
if (!iter->isEmpty())
newRecord.mData.mWeight += iter->get<ESM::Ingredient>()->mBase->mData.mWeight;
2013-11-10 20:45:27 +00:00
if (countIngredients() > 0)
newRecord.mData.mWeight /= countIngredients();
2013-11-10 20:45:27 +00:00
newRecord.mData.mValue = mValue;
newRecord.mData.mAutoCalc = 0;
2013-11-10 20:45:27 +00:00
newRecord.mName = name;
2015-04-22 15:58:55 +00:00
int index = Misc::Rng::rollDice(6);
assert (index>=0 && index<6);
2013-11-10 20:45:27 +00:00
static const char *meshes[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" };
2013-11-10 20:45:27 +00:00
newRecord.mModel = "m\\misc_potion_" + std::string (meshes[index]) + "_01.nif";
newRecord.mIcon = "m\\tx_potion_" + std::string (meshes[index]) + "_01.dds";
2013-11-10 20:45:27 +00:00
newRecord.mEffects.mList = mEffects;
2013-11-10 20:45:27 +00:00
[General] Implement RecordDynamic packet, part 1 Spell, potion, enchantment, creature, NPC, armor, book, clothing, miscellaneous and weapon record data can now be sent in a RecordDynamic packet. Additionally, the packets include data related to associated magical effects (for spells, potions and enchantments), data related to default inventory contents (for creatures and NPCs) and data related to body parts affected (for armor and clothing). The server now has associated script functions for setting most of the details of the above, with the main exception being individual creature and NPC stats. Records can either be created entirely from scratch or can use an existing record (set via the baseId variable) as a starting point for their values. In the latter case, only the values that are specifically set override the starting values. Creature and NPC records also have an inventoryBaseId that can be used on top of the baseId to base their inventories on another existing record. The client's RecordHelper class has been heavily expanded to allow for the above mentioned functionality. When players create spells, potions and enchantments as part of regular gameplay, they send RecordDynamic packets that provide the server with the complete details of the records that should be created. When they create enchantments, they also provide the server with armor, book, clothing and weapon records corresponding to the items they've enchanted. This functionality added by this packet was originally supposed to be exclusive to the rewrite, but I've gone ahead and tried to provide it for the pre-rewrite in a way that can mostly be reused for the rewrite.
2018-07-30 07:56:26 +00:00
/*
Start of tes3mp change (major)
Don't create a record and don't add the potion to the player's inventory;
instead just store its record in preparation for sending it to the server
and expect the server to add it to the player's inventory
[General] Implement RecordDynamic packet, part 1 Spell, potion, enchantment, creature, NPC, armor, book, clothing, miscellaneous and weapon record data can now be sent in a RecordDynamic packet. Additionally, the packets include data related to associated magical effects (for spells, potions and enchantments), data related to default inventory contents (for creatures and NPCs) and data related to body parts affected (for armor and clothing). The server now has associated script functions for setting most of the details of the above, with the main exception being individual creature and NPC stats. Records can either be created entirely from scratch or can use an existing record (set via the baseId variable) as a starting point for their values. In the latter case, only the values that are specifically set override the starting values. Creature and NPC records also have an inventoryBaseId that can be used on top of the baseId to base their inventories on another existing record. The client's RecordHelper class has been heavily expanded to allow for the above mentioned functionality. When players create spells, potions and enchantments as part of regular gameplay, they send RecordDynamic packets that provide the server with the complete details of the records that should be created. When they create enchantments, they also provide the server with armor, book, clothing and weapon records corresponding to the items they've enchanted. This functionality added by this packet was originally supposed to be exclusive to the rewrite, but I've gone ahead and tried to provide it for the pre-rewrite in a way that can mostly be reused for the rewrite.
2018-07-30 07:56:26 +00:00
*/
/*
const ESM::Potion* record = getRecord(newRecord);
if (!record)
[General] Implement RecordDynamic packet, part 1 Spell, potion, enchantment, creature, NPC, armor, book, clothing, miscellaneous and weapon record data can now be sent in a RecordDynamic packet. Additionally, the packets include data related to associated magical effects (for spells, potions and enchantments), data related to default inventory contents (for creatures and NPCs) and data related to body parts affected (for armor and clothing). The server now has associated script functions for setting most of the details of the above, with the main exception being individual creature and NPC stats. Records can either be created entirely from scratch or can use an existing record (set via the baseId variable) as a starting point for their values. In the latter case, only the values that are specifically set override the starting values. Creature and NPC records also have an inventoryBaseId that can be used on top of the baseId to base their inventories on another existing record. The client's RecordHelper class has been heavily expanded to allow for the above mentioned functionality. When players create spells, potions and enchantments as part of regular gameplay, they send RecordDynamic packets that provide the server with the complete details of the records that should be created. When they create enchantments, they also provide the server with armor, book, clothing and weapon records corresponding to the items they've enchanted. This functionality added by this packet was originally supposed to be exclusive to the rewrite, but I've gone ahead and tried to provide it for the pre-rewrite in a way that can mostly be reused for the rewrite.
2018-07-30 07:56:26 +00:00
{
record = MWBase::Environment::get().getWorld()->createRecord(newRecord);
}
2013-11-10 20:45:27 +00:00
mAlchemist.getClass().getContainerStore (mAlchemist).add (record->mId, 1, mAlchemist);
[General] Implement RecordDynamic packet, part 1 Spell, potion, enchantment, creature, NPC, armor, book, clothing, miscellaneous and weapon record data can now be sent in a RecordDynamic packet. Additionally, the packets include data related to associated magical effects (for spells, potions and enchantments), data related to default inventory contents (for creatures and NPCs) and data related to body parts affected (for armor and clothing). The server now has associated script functions for setting most of the details of the above, with the main exception being individual creature and NPC stats. Records can either be created entirely from scratch or can use an existing record (set via the baseId variable) as a starting point for their values. In the latter case, only the values that are specifically set override the starting values. Creature and NPC records also have an inventoryBaseId that can be used on top of the baseId to base their inventories on another existing record. The client's RecordHelper class has been heavily expanded to allow for the above mentioned functionality. When players create spells, potions and enchantments as part of regular gameplay, they send RecordDynamic packets that provide the server with the complete details of the records that should be created. When they create enchantments, they also provide the server with armor, book, clothing and weapon records corresponding to the items they've enchanted. This functionality added by this packet was originally supposed to be exclusive to the rewrite, but I've gone ahead and tried to provide it for the pre-rewrite in a way that can mostly be reused for the rewrite.
2018-07-30 07:56:26 +00:00
*/
mStoredPotion = newRecord;
[General] Implement RecordDynamic packet, part 1 Spell, potion, enchantment, creature, NPC, armor, book, clothing, miscellaneous and weapon record data can now be sent in a RecordDynamic packet. Additionally, the packets include data related to associated magical effects (for spells, potions and enchantments), data related to default inventory contents (for creatures and NPCs) and data related to body parts affected (for armor and clothing). The server now has associated script functions for setting most of the details of the above, with the main exception being individual creature and NPC stats. Records can either be created entirely from scratch or can use an existing record (set via the baseId variable) as a starting point for their values. In the latter case, only the values that are specifically set override the starting values. Creature and NPC records also have an inventoryBaseId that can be used on top of the baseId to base their inventories on another existing record. The client's RecordHelper class has been heavily expanded to allow for the above mentioned functionality. When players create spells, potions and enchantments as part of regular gameplay, they send RecordDynamic packets that provide the server with the complete details of the records that should be created. When they create enchantments, they also provide the server with armor, book, clothing and weapon records corresponding to the items they've enchanted. This functionality added by this packet was originally supposed to be exclusive to the rewrite, but I've gone ahead and tried to provide it for the pre-rewrite in a way that can mostly be reused for the rewrite.
2018-07-30 07:56:26 +00:00
/*
End of tes3mp change (major)
*/
}
void MWMechanics::Alchemy::increaseSkill()
{
mAlchemist.getClass().skillUsageSucceeded (mAlchemist, ESM::Skill::Alchemy, 0);
}
2014-10-02 11:54:56 +00:00
float MWMechanics::Alchemy::getAlchemyFactor() const
{
const CreatureStats& creatureStats = mAlchemist.getClass().getCreatureStats (mAlchemist);
2013-11-10 20:45:27 +00:00
return
2018-10-08 14:06:30 +00:00
(mAlchemist.getClass().getSkill(mAlchemist, ESM::Skill::Alchemy) +
0.1f * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified()
+ 0.1f * creatureStats.getAttribute (ESM::Attribute::Luck).getModified());
}
int MWMechanics::Alchemy::countIngredients() const
{
int ingredients = 0;
for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter)
if (!iter->isEmpty())
++ingredients;
return ingredients;
}
2018-09-18 10:57:21 +00:00
int MWMechanics::Alchemy::countPotionsToBrew() const
{
Result readyStatus = getReadyStatus();
if (readyStatus != Result_Success)
return 0;
int toBrew = -1;
for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter)
if (!iter->isEmpty())
{
int count = iter->getRefData().getCount();
if ((count > 0 && count < toBrew) || toBrew < 0)
toBrew = count;
}
return toBrew;
}
2012-09-29 08:02:46 +00:00
void MWMechanics::Alchemy::setAlchemist (const MWWorld::Ptr& npc)
{
mAlchemist = npc;
2013-11-10 20:45:27 +00:00
mIngredients.resize (4);
std::fill (mIngredients.begin(), mIngredients.end(), MWWorld::Ptr());
2013-11-10 20:45:27 +00:00
mTools.resize (4);
2013-11-10 20:45:27 +00:00
std::fill (mTools.begin(), mTools.end(), MWWorld::Ptr());
2013-11-10 20:45:27 +00:00
mEffects.clear();
2013-11-10 20:45:27 +00:00
MWWorld::ContainerStore& store = npc.getClass().getContainerStore (npc);
2013-11-10 20:45:27 +00:00
for (MWWorld::ContainerStoreIterator iter (store.begin (MWWorld::ContainerStore::Type_Apparatus));
iter!=store.end(); ++iter)
2013-11-10 20:45:27 +00:00
{
MWWorld::LiveCellRef<ESM::Apparatus>* ref = iter->get<ESM::Apparatus>();
2013-11-10 20:45:27 +00:00
2012-11-05 12:07:59 +00:00
int type = ref->mBase->mData.mType;
2013-11-10 20:45:27 +00:00
if (type<0 || type>=static_cast<int> (mTools.size()))
throw std::runtime_error ("invalid apparatus type");
2013-11-10 20:45:27 +00:00
if (!mTools[type].isEmpty())
2012-11-05 12:07:59 +00:00
if (ref->mBase->mData.mQuality<=mTools[type].get<ESM::Apparatus>()->mBase->mData.mQuality)
continue;
2013-11-10 20:45:27 +00:00
mTools[type] = *iter;
}
}
MWMechanics::Alchemy::TToolsIterator MWMechanics::Alchemy::beginTools() const
{
return mTools.begin();
}
MWMechanics::Alchemy::TToolsIterator MWMechanics::Alchemy::endTools() const
{
return mTools.end();
2012-09-29 08:02:46 +00:00
}
MWMechanics::Alchemy::TIngredientsIterator MWMechanics::Alchemy::beginIngredients() const
{
return mIngredients.begin();
}
MWMechanics::Alchemy::TIngredientsIterator MWMechanics::Alchemy::endIngredients() const
{
return mIngredients.end();
}
void MWMechanics::Alchemy::clear()
{
mAlchemist = MWWorld::Ptr();
mTools.clear();
mIngredients.clear();
mEffects.clear();
2018-09-18 10:57:21 +00:00
setPotionName("");
}
void MWMechanics::Alchemy::setPotionName(const std::string& name)
{
mPotionName = name;
}
int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient)
{
// find a free slot
int slot = -1;
for (int i=0; i<static_cast<int> (mIngredients.size()); ++i)
if (mIngredients[i].isEmpty())
{
slot = i;
break;
2013-11-10 20:45:27 +00:00
}
if (slot==-1)
return -1;
2013-11-10 20:45:27 +00:00
for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter)
2015-12-18 15:58:08 +00:00
if (!iter->isEmpty() && Misc::StringUtils::ciEqual(ingredient.getCellRef().getRefId(),
iter->getCellRef().getRefId()))
return -1;
2013-11-10 20:45:27 +00:00
mIngredients[slot] = ingredient;
2013-11-10 20:45:27 +00:00
updateEffects();
2013-11-10 20:45:27 +00:00
return slot;
}
void MWMechanics::Alchemy::removeIngredient (int index)
{
if (index>=0 && index<static_cast<int> (mIngredients.size()))
{
mIngredients[index] = MWWorld::Ptr();
updateEffects();
}
}
MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::beginEffects() const
{
return mEffects.begin();
}
MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::endEffects() const
{
return mEffects.end();
}
bool MWMechanics::Alchemy::knownEffect(unsigned int potionEffectIndex, const MWWorld::Ptr &npc)
{
float alchemySkill = npc.getClass().getSkill (npc, ESM::Skill::Alchemy);
static const float fWortChanceValue =
2018-08-29 15:38:12 +00:00
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fWortChanceValue")->mValue.getFloat();
return (potionEffectIndex <= 1 && alchemySkill >= fWortChanceValue)
|| (potionEffectIndex <= 3 && alchemySkill >= fWortChanceValue*2)
|| (potionEffectIndex <= 5 && alchemySkill >= fWortChanceValue*3)
|| (potionEffectIndex <= 7 && alchemySkill >= fWortChanceValue*4);
}
2018-09-18 10:57:21 +00:00
MWMechanics::Alchemy::Result MWMechanics::Alchemy::getReadyStatus() const
{
if (mTools[ESM::Apparatus::MortarPestle].isEmpty())
return Result_NoMortarAndPestle;
2013-11-10 20:45:27 +00:00
if (countIngredients()<2)
return Result_LessThanTwoIngredients;
2018-09-18 10:57:21 +00:00
if (mPotionName.empty())
return Result_NoName;
2013-11-10 20:45:27 +00:00
if (listEffects().empty())
return Result_NoEffects;
2018-09-18 10:57:21 +00:00
return Result_Success;
}
MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& name, int& count)
{
/*
Start of tes3mp addition
Instead of sending an ID_PLAYER_INVENTORY packet for every ingredient removal in
ContainerStore::remove(), as that would get very spammy when many potions are created
at the same time, just avoid sending packets here and store the item removals so they
can be sent in a single packet when all the potions have been created
*/
mwmp::Main::get().getLocalPlayer()->avoidSendingInventoryPackets = true;
/*
End of tes3mp addition
*/
2018-09-18 10:57:21 +00:00
setPotionName(name);
Result readyStatus = getReadyStatus();
if (readyStatus == Result_NoEffects)
removeIngredients();
if (readyStatus != Result_Success)
return readyStatus;
Result result = Result_RandomFailure;
int brewedCount = 0;
for (int i = 0; i < count; ++i)
{
if (createSingle() == Result_Success)
{
result = Result_Success;
brewedCount++;
}
}
2018-09-18 10:57:21 +00:00
count = brewedCount;
/*
Start of tes3mp addition
Send an ID_RECORD_DYNAMIC packet with the potion we've been creating
now that we know its quantity
Stop avoiding the sending of ID_PLAYER_INVENTORY packets and send all
item removals stored so far
*/
mwmp::Main::get().getNetworking()->getWorldstate()->sendPotionRecord(&mStoredPotion, brewedCount);
mwmp::Main::get().getLocalPlayer()->avoidSendingInventoryPackets = false;
mwmp::Main::get().getLocalPlayer()->sendStoredItemRemovals();
/*
End of tes3mp addition
*/
2018-09-18 10:57:21 +00:00
return result;
}
MWMechanics::Alchemy::Result MWMechanics::Alchemy::createSingle ()
{
2014-10-02 11:54:56 +00:00
if (beginEffects() == endEffects())
{
// all effects were nullified due to insufficient skill
removeIngredients();
return Result_RandomFailure;
}
2015-04-22 15:58:55 +00:00
if (getAlchemyFactor() < Misc::Rng::roll0to99())
{
removeIngredients();
return Result_RandomFailure;
}
2018-09-18 10:57:21 +00:00
addPotion(mPotionName);
removeIngredients();
2013-11-10 20:45:27 +00:00
increaseSkill();
return Result_Success;
}
2014-10-20 15:28:22 +00:00
std::string MWMechanics::Alchemy::suggestPotionName()
{
std::set<MWMechanics::EffectKey> effects = listEffects();
if (effects.empty())
return "";
int effectId = effects.begin()->mId;
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
2018-08-29 15:38:12 +00:00
ESM::MagicEffect::effectIdToString(effectId))->mValue.getString();
2014-10-20 15:28:22 +00:00
}
std::vector<std::string> MWMechanics::Alchemy::effectsDescription (const MWWorld::ConstPtr &ptr, const int alchemySkill)
{
std::vector<std::string> effects;
const auto& item = ptr.get<ESM::Ingredient>()->mBase;
const auto& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
const static auto fWortChanceValue = gmst.find("fWortChanceValue")->mValue.getFloat();
const auto& data = item->mData;
for (auto i = 0; i < 4; ++i)
{
const auto effectID = data.mEffectID[i];
const auto skillID = data.mSkills[i];
const auto attributeID = data.mAttributes[i];
if (alchemySkill < fWortChanceValue * (i + 1))
break;
if (effectID != -1)
{
std::string effect = gmst.find(ESM::MagicEffect::effectIdToString(effectID))->mValue.getString();
if (skillID != -1)
effect += " " + gmst.find(ESM::Skill::sSkillNameIds[skillID])->mValue.getString();
else if (attributeID != -1)
effect += " " + gmst.find(ESM::Attribute::sGmstAttributeIds[attributeID])->mValue.getString();
effects.push_back(effect);
}
}
return effects;
}