mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-26 09:35:28 +00:00
Issue #61: potion creation (1st part; still missing some implementations)
This commit is contained in:
parent
332039da10
commit
5fbca239dd
@ -1,15 +1,259 @@
|
|||||||
|
|
||||||
#include "alchemy.hpp"
|
#include "alchemy.hpp"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <components/esm/loadskil.hpp>
|
||||||
|
#include <components/esm/loadappa.hpp>
|
||||||
|
#include <components/esm/loadgmst.hpp>
|
||||||
|
#include <components/esm/loadmgef.hpp>
|
||||||
|
|
||||||
|
#include <components/esm_store/store.hpp>
|
||||||
|
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
#include "../mwworld/containerstore.hpp"
|
#include "../mwworld/containerstore.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
|
||||||
|
#include "magiceffects.hpp"
|
||||||
|
#include "creaturestats.hpp"
|
||||||
|
#include "npcstats.hpp"
|
||||||
|
|
||||||
|
std::set<MWMechanics::EffectKey> MWMechanics::Alchemy::listEffects() const
|
||||||
|
{
|
||||||
|
std::set<EffectKey> effects;
|
||||||
|
|
||||||
|
for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter)
|
||||||
|
{
|
||||||
|
if (!iter->isEmpty())
|
||||||
|
{
|
||||||
|
const MWWorld::LiveCellRef<ESM::Ingredient> *ingredient = iter->get<ESM::Ingredient>();
|
||||||
|
|
||||||
|
for (int i=0; i<4; ++i)
|
||||||
|
if (ingredient->base->mData.mEffectID[i]!=-1)
|
||||||
|
effects.insert (EffectKey (
|
||||||
|
ingredient->base->mData.mEffectID[i], ingredient->base->mData.mSkills[i]!=-1 ?
|
||||||
|
ingredient->base->mData.mSkills[i] : ingredient->base->mData.mAttributes[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return effects;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MWMechanics::Alchemy::filterEffects (std::set<EffectKey>& effects) const
|
||||||
|
{
|
||||||
|
std::set<EffectKey>::iterator iter = effects.begin();
|
||||||
|
|
||||||
|
while (iter!=effects.end())
|
||||||
|
{
|
||||||
|
bool remove = false;
|
||||||
|
|
||||||
|
const EffectKey& key = *iter;
|
||||||
|
|
||||||
|
{ // dodge pointless g++ warning
|
||||||
|
for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
const MWWorld::LiveCellRef<ESM::Ingredient> *ingredient = iter->get<ESM::Ingredient>();
|
||||||
|
|
||||||
|
for (int i=0; i<4; ++i)
|
||||||
|
if (key.mId==ingredient->base->mData.mEffectID[i] &&
|
||||||
|
(key.mArg==ingredient->base->mData.mSkills[i] ||
|
||||||
|
key.mArg==ingredient->base->mData.mAttributes[i]))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
remove = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remove)
|
||||||
|
effects.erase (iter++);
|
||||||
|
else
|
||||||
|
++iter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::Negative | ESM::MagicEffect::Negative2);
|
||||||
|
|
||||||
|
int tool = negative ? ESM::Apparatus::Retort : ESM::Apparatus::Albemic;
|
||||||
|
|
||||||
|
int setup = 0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
float toolQuality = setup==1 || setup==2 ? mTools[tool].get<ESM::Apparatus>()->base->mData.mQuality : 0;
|
||||||
|
float calcinatorQuality = setup==1 || setup==3 ?
|
||||||
|
mTools[ESM::Apparatus::Calcinator].get<ESM::Apparatus>()->base->mData.mQuality : 0;
|
||||||
|
|
||||||
|
float quality = 1;
|
||||||
|
|
||||||
|
switch (setup)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
|
||||||
|
quality = negative ? 2 * toolQuality + 3 * calcinatorQuality :
|
||||||
|
(magnitude && duration ?
|
||||||
|
2 * toolQuality + calcinatorQuality : 2/3.0 * (toolQuality + calcinatorQuality) + 0.5);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
|
||||||
|
quality = negative ? 1+toolQuality : (magnitude && duration ? toolQuality : toolQuality + 0.5);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
|
||||||
|
quality = magnitude && duration ? calcinatorQuality : calcinatorQuality + 0.5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setup==3 || !negative)
|
||||||
|
{
|
||||||
|
value += quality;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (quality==0)
|
||||||
|
throw std::runtime_error ("invalid derived alchemy apparatus quality");
|
||||||
|
|
||||||
|
value /= quality;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MWMechanics::Alchemy::updateEffects()
|
||||||
|
{
|
||||||
|
mEffects.clear();
|
||||||
|
|
||||||
|
int ingredients = 0;
|
||||||
|
|
||||||
|
for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter)
|
||||||
|
if (!iter->isEmpty())
|
||||||
|
++ingredients;
|
||||||
|
|
||||||
|
if (ingredients<2 || mAlchemist.isEmpty() || mTools[ESM::Apparatus::MortarPestle].isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// find effects
|
||||||
|
std::set<EffectKey> effects (listEffects());
|
||||||
|
filterEffects (effects);
|
||||||
|
|
||||||
|
// general alchemy factor
|
||||||
|
float x = getChance();
|
||||||
|
|
||||||
|
x *= mTools[ESM::Apparatus::MortarPestle].get<ESM::Apparatus>()->base->mData.mQuality;
|
||||||
|
x *= MWBase::Environment::get().getWorld()->getStore().gameSettings.find ("fPotionStrengthMult")->getFloat();
|
||||||
|
|
||||||
|
// build quantified effect list
|
||||||
|
for (std::set<EffectKey>::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter)
|
||||||
|
{
|
||||||
|
const ESM::MagicEffect *magicEffect =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().magicEffects.find (iter->mId);
|
||||||
|
|
||||||
|
if (magicEffect->mData.mBaseCost<=0)
|
||||||
|
throw std::runtime_error ("invalid base cost for magic effect " + iter->mId);
|
||||||
|
|
||||||
|
float fPotionT1MagMul =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().gameSettings.find ("fPotionT1MagMul")->getFloat();
|
||||||
|
|
||||||
|
if (fPotionT1MagMul<=0)
|
||||||
|
throw std::runtime_error ("invalid gmst: fPotionT1MagMul");
|
||||||
|
|
||||||
|
float fPotionT1DurMult =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().gameSettings.find ("fPotionT1DurMult")->getFloat();
|
||||||
|
|
||||||
|
if (fPotionT1DurMult<=0)
|
||||||
|
throw std::runtime_error ("invalid gmst: fPotionT1DurMult");
|
||||||
|
|
||||||
|
float magnitude = magicEffect->mData.mFlags && ESM::MagicEffect::NoMagnitude ?
|
||||||
|
1 : (x / fPotionT1MagMul) / magicEffect->mData.mBaseCost;
|
||||||
|
float duration = magicEffect->mData.mFlags && ESM::MagicEffect::NoDuration ?
|
||||||
|
1 : (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);
|
||||||
|
|
||||||
|
duration = static_cast<int> (duration+0.5);
|
||||||
|
magnitude = static_cast<int> (magnitude+0.5);
|
||||||
|
|
||||||
|
if (magnitude>0 && duration>0)
|
||||||
|
{
|
||||||
|
ESM::ENAMstruct effect;
|
||||||
|
effect.mEffectID = iter->mId;
|
||||||
|
|
||||||
|
effect.mSkill = effect.mAttribute = iter->mArg; // somewhat hack-ish, but should work
|
||||||
|
|
||||||
|
effect.mRange = 0;
|
||||||
|
effect.mArea = 0;
|
||||||
|
|
||||||
|
effect.mDuration = duration;
|
||||||
|
effect.mMagnMin = effect.mMagnMax = magnitude;
|
||||||
|
|
||||||
|
mEffects.push_back (effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ESM::Potion *MWMechanics::Alchemy::getRecord() const
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void MWMechanics::Alchemy::removeIngredients()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void MWMechanics::Alchemy::addPotion (const std::string& name)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void MWMechanics::Alchemy::increaseSkill()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
float MWMechanics::Alchemy::getChance() const
|
||||||
|
{
|
||||||
|
const CreatureStats& creatureStats = MWWorld::Class::get (mAlchemist).getCreatureStats (mAlchemist);
|
||||||
|
const NpcStats& npcStats = MWWorld::Class::get (mAlchemist).getNpcStats (mAlchemist);
|
||||||
|
|
||||||
|
return
|
||||||
|
(npcStats.getSkill (ESM::Skill::Alchemy).getModified() +
|
||||||
|
0.1 * creatureStats.getAttribute (1).getModified()
|
||||||
|
+ 0.1 * creatureStats.getAttribute (7).getModified());
|
||||||
|
}
|
||||||
|
|
||||||
void MWMechanics::Alchemy::setAlchemist (const MWWorld::Ptr& npc)
|
void MWMechanics::Alchemy::setAlchemist (const MWWorld::Ptr& npc)
|
||||||
{
|
{
|
||||||
mNpc = npc;
|
mAlchemist = npc;
|
||||||
|
|
||||||
mIngredients.resize (4);
|
mIngredients.resize (4);
|
||||||
|
|
||||||
@ -19,6 +263,8 @@ void MWMechanics::Alchemy::setAlchemist (const MWWorld::Ptr& npc)
|
|||||||
|
|
||||||
std::fill (mTools.begin(), mTools.end(), MWWorld::Ptr());
|
std::fill (mTools.begin(), mTools.end(), MWWorld::Ptr());
|
||||||
|
|
||||||
|
mEffects.clear();
|
||||||
|
|
||||||
MWWorld::ContainerStore& store = MWWorld::Class::get (npc).getContainerStore (npc);
|
MWWorld::ContainerStore& store = MWWorld::Class::get (npc).getContainerStore (npc);
|
||||||
|
|
||||||
for (MWWorld::ContainerStoreIterator iter (store.begin (MWWorld::ContainerStore::Type_Apparatus));
|
for (MWWorld::ContainerStoreIterator iter (store.begin (MWWorld::ContainerStore::Type_Apparatus));
|
||||||
@ -61,9 +307,10 @@ MWMechanics::Alchemy::TIngredientsIterator MWMechanics::Alchemy::endIngredients(
|
|||||||
|
|
||||||
void MWMechanics::Alchemy::clear()
|
void MWMechanics::Alchemy::clear()
|
||||||
{
|
{
|
||||||
mNpc = MWWorld::Ptr();
|
mAlchemist = MWWorld::Ptr();
|
||||||
mTools.clear();
|
mTools.clear();
|
||||||
mIngredients.clear();
|
mIngredients.clear();
|
||||||
|
mEffects.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient)
|
int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient)
|
||||||
@ -86,6 +333,8 @@ int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient)
|
|||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
mIngredients[slot] = ingredient;
|
mIngredients[slot] = ingredient;
|
||||||
|
|
||||||
|
updateEffects();
|
||||||
|
|
||||||
return slot;
|
return slot;
|
||||||
}
|
}
|
||||||
@ -93,6 +342,61 @@ int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient)
|
|||||||
void MWMechanics::Alchemy::removeIngredient (int index)
|
void MWMechanics::Alchemy::removeIngredient (int index)
|
||||||
{
|
{
|
||||||
if (index>=0 && index<static_cast<int> (mIngredients.size()))
|
if (index>=0 && index<static_cast<int> (mIngredients.size()))
|
||||||
|
{
|
||||||
mIngredients[index] = MWWorld::Ptr();
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MWMechanics::Alchemy::getPotionName() const
|
||||||
|
{
|
||||||
|
if (const ESM::Potion *potion = getRecord())
|
||||||
|
return potion->mName;
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& name)
|
||||||
|
{
|
||||||
|
if (mTools[ESM::Apparatus::MortarPestle].isEmpty())
|
||||||
|
return Result_NoMortarAndPestle;
|
||||||
|
|
||||||
|
int ingredients = 0;
|
||||||
|
|
||||||
|
for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter)
|
||||||
|
if (!iter->isEmpty())
|
||||||
|
++ingredients;
|
||||||
|
|
||||||
|
if (ingredients<2)
|
||||||
|
return Result_LessThanTwoIngredients;
|
||||||
|
|
||||||
|
if (name.empty() && getPotionName().empty())
|
||||||
|
return Result_NoName;
|
||||||
|
|
||||||
|
if (beginEffects()==endEffects())
|
||||||
|
return Result_NoEffects;
|
||||||
|
|
||||||
|
if (getChance()<std::rand()/static_cast<double> (RAND_MAX)*100)
|
||||||
|
{
|
||||||
|
removeIngredients();
|
||||||
|
return Result_RandomFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
addPotion (name);
|
||||||
|
|
||||||
|
removeIngredients();
|
||||||
|
|
||||||
|
increaseSkill();
|
||||||
|
|
||||||
|
return Result_Success;
|
||||||
|
}
|
||||||
|
@ -2,11 +2,16 @@
|
|||||||
#define GAME_MWMECHANICS_ALCHEMY_H
|
#define GAME_MWMECHANICS_ALCHEMY_H
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <components/esm/effectlist.hpp>
|
||||||
|
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
struct EffectKey;
|
||||||
|
|
||||||
/// \brief Potion creation via alchemy skill
|
/// \brief Potion creation via alchemy skill
|
||||||
class Alchemy
|
class Alchemy
|
||||||
{
|
{
|
||||||
@ -17,13 +22,54 @@ namespace MWMechanics
|
|||||||
|
|
||||||
typedef std::vector<MWWorld::Ptr> TIngredientsContainer;
|
typedef std::vector<MWWorld::Ptr> TIngredientsContainer;
|
||||||
typedef TIngredientsContainer::const_iterator TIngredientsIterator;
|
typedef TIngredientsContainer::const_iterator TIngredientsIterator;
|
||||||
|
|
||||||
|
typedef std::vector<ESM::ENAMstruct> TEffectsContainer;
|
||||||
|
typedef TEffectsContainer::const_iterator TEffectsIterator;
|
||||||
|
|
||||||
|
enum Result
|
||||||
|
{
|
||||||
|
Result_Success,
|
||||||
|
|
||||||
|
Result_NoMortarAndPestle,
|
||||||
|
Result_LessThanTwoIngredients,
|
||||||
|
Result_NoName,
|
||||||
|
Result_NoEffects,
|
||||||
|
Result_RandomFailure
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
MWWorld::Ptr mNpc;
|
MWWorld::Ptr mAlchemist;
|
||||||
TToolsContainer mTools;
|
TToolsContainer mTools;
|
||||||
TIngredientsContainer mIngredients;
|
TIngredientsContainer mIngredients;
|
||||||
|
TEffectsContainer mEffects;
|
||||||
|
|
||||||
|
std::set<EffectKey> listEffects() const;
|
||||||
|
///< List all effects of all used ingredients.
|
||||||
|
|
||||||
|
void filterEffects (std::set<EffectKey>& effects) const;
|
||||||
|
///< Filter out effects not shared by all ingredients.
|
||||||
|
|
||||||
|
void applyTools (int flags, float& value) const;
|
||||||
|
|
||||||
|
void updateEffects();
|
||||||
|
|
||||||
|
const ESM::Potion *getRecord() const;
|
||||||
|
///< Return existing recrod for created potion (may return 0)
|
||||||
|
|
||||||
|
void removeIngredients();
|
||||||
|
///< Remove selected ingredients from alchemist's inventory, cleanup selected ingredients and
|
||||||
|
/// update effect list accordingly.
|
||||||
|
|
||||||
|
void addPotion (const std::string& name);
|
||||||
|
///< Add a potion to the alchemist's inventory.
|
||||||
|
|
||||||
|
void increaseSkill();
|
||||||
|
///< Increase alchemist's skill.
|
||||||
|
|
||||||
|
float getChance() const;
|
||||||
|
///< Return chance of success.
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void setAlchemist (const MWWorld::Ptr& npc);
|
void setAlchemist (const MWWorld::Ptr& npc);
|
||||||
@ -49,6 +95,20 @@ namespace MWMechanics
|
|||||||
|
|
||||||
void removeIngredient (int index);
|
void removeIngredient (int index);
|
||||||
///< Remove ingredient from slot (calling this function on an empty slot is a no-op).
|
///< Remove ingredient from slot (calling this function on an empty slot is a no-op).
|
||||||
|
|
||||||
|
TEffectsIterator beginEffects() const;
|
||||||
|
|
||||||
|
TEffectsIterator endEffects() const;
|
||||||
|
|
||||||
|
std::string getPotionName() const;
|
||||||
|
///< Return the name of the potion that would be created when calling create (if a record for such
|
||||||
|
/// a potion already exists) or return an empty string.
|
||||||
|
|
||||||
|
Result create (const std::string& name);
|
||||||
|
///< Try to create a potion from the ingredients, place it in the inventory of the alchemist and
|
||||||
|
/// adjust the skills of the alchemist accordingly.
|
||||||
|
/// \param name must not be an empty string, unless there is already a potion record (
|
||||||
|
/// getPotionName() does not return an empty string).
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ struct MagicEffect
|
|||||||
enum Flags
|
enum Flags
|
||||||
{
|
{
|
||||||
NoDuration = 0x4,
|
NoDuration = 0x4,
|
||||||
|
NoMagnitude = 0x8,
|
||||||
|
Negative2 = 0x10,
|
||||||
SpellMaking = 0x0200,
|
SpellMaking = 0x0200,
|
||||||
Enchanting = 0x0400,
|
Enchanting = 0x0400,
|
||||||
Negative = 0x0800 // A harmful effect. Will determine whether
|
Negative = 0x0800 // A harmful effect. Will determine whether
|
||||||
|
Loading…
x
Reference in New Issue
Block a user