Refactor ActiveSpells to track range type. Added basic self range magic.

This commit is contained in:
scrawl 2013-11-09 07:51:46 +01:00
parent 9e2b1942fc
commit cbe96a2170
10 changed files with 187 additions and 29 deletions

View file

@ -406,6 +406,8 @@ namespace MWBase
virtual bool getGodModeState() = 0; virtual bool getGodModeState() = 0;
virtual bool toggleGodMode() = 0; virtual bool toggleGodMode() = 0;
virtual void castSpell (const MWWorld::Ptr& actor) = 0;
}; };
} }

View file

@ -97,6 +97,8 @@ namespace MWGui
} }
// add lasting effect spells/potions etc // add lasting effect spells/potions etc
// TODO: Move this to ActiveSpells
const MWMechanics::ActiveSpells::TContainer& activeSpells = stats.getActiveSpells().getActiveSpells(); const MWMechanics::ActiveSpells::TContainer& activeSpells = stats.getActiveSpells().getActiveSpells();
for (MWMechanics::ActiveSpells::TContainer::const_iterator it = activeSpells.begin(); for (MWMechanics::ActiveSpells::TContainer::const_iterator it = activeSpells.begin();
it != activeSpells.end(); ++it) it != activeSpells.end(); ++it)
@ -105,31 +107,36 @@ namespace MWGui
float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
int i=0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = list.mList.begin(); for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = list.mList.begin();
effectIt != list.mList.end(); ++effectIt) effectIt != list.mList.end(); ++effectIt, ++i)
{ {
if (effectIt->mRange != it->second.mRange)
continue;
float randomFactor = it->second.mRandom[i];
const ESM::MagicEffect* magicEffect = const ESM::MagicEffect* magicEffect =
MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(effectIt->mEffectID); MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(effectIt->mEffectID);
MagicEffectInfo effectInfo; MagicEffectInfo effectInfo;
effectInfo.mSource = getSpellDisplayName (it->first); effectInfo.mSource = it->second.mName;
effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID);
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)
effectInfo.mKey.mArg = effectIt->mSkill; effectInfo.mKey.mArg = effectIt->mSkill;
else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)
effectInfo.mKey.mArg = effectIt->mAttribute; effectInfo.mKey.mArg = effectIt->mAttribute;
effectInfo.mMagnitude = effectIt->mMagnMin + (effectIt->mMagnMax-effectIt->mMagnMin) * it->second.second; effectInfo.mMagnitude = effectIt->mMagnMin + (effectIt->mMagnMax-effectIt->mMagnMin) * randomFactor;
effectInfo.mRemainingTime = effectIt->mDuration + effectInfo.mRemainingTime = effectIt->mDuration +
(it->second.first - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale;
// ingredients need special casing for their magnitude / duration // ingredients need special casing for their magnitude / duration
/// \todo duplicated from ActiveSpells, helper function?
if (MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (it->first)) if (MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (it->first))
{ {
effectInfo.mRemainingTime = effectIt->mDuration * it->second.second + effectInfo.mRemainingTime = effectIt->mDuration * randomFactor +
(it->second.first - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale;
effectInfo.mMagnitude = static_cast<int> (0.05*it->second.second / (0.1 * magicEffect->mData.mBaseCost)); effectInfo.mMagnitude = static_cast<int> (0.05*randomFactor / (0.1 * magicEffect->mData.mBaseCost));
} }
effects[effectIt->mEffectID].push_back (effectInfo); effects[effectIt->mEffectID].push_back (effectInfo);
@ -288,6 +295,10 @@ namespace MWGui
ESM::EffectList SpellIcons::getSpellEffectList (const std::string& id) ESM::EffectList SpellIcons::getSpellEffectList (const std::string& id)
{ {
if (const ESM::Enchantment* enchantment =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().search (id))
return enchantment->mEffects;
if (const ESM::Spell *spell = if (const ESM::Spell *spell =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id)) MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
return spell->mEffects; return spell->mEffects;

View file

@ -467,7 +467,7 @@ namespace MWGui
} }
Widgets::MWDynamicStatPtr chargeWidget = enchantArea->createWidget<Widgets::MWDynamicStat> Widgets::MWDynamicStatPtr chargeWidget = enchantArea->createWidget<Widgets::MWDynamicStat>
("MW_ChargeBar", chargeCoord, MyGUI::Align::Default, "ToolTipEnchantCharge"); ("MW_ChargeBar", chargeCoord, MyGUI::Align::Default, "ToolTipEnchantCharge");
chargeWidget->setValue(charge, charge); chargeWidget->setValue(charge, maxCharge);
totalSize.height += 24; totalSize.height += 24;
} }
} }

View file

@ -15,6 +15,7 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
@ -64,15 +65,19 @@ namespace MWMechanics
{ {
std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (iter->first); std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (iter->first);
const MWWorld::TimeStamp& start = iter->second.first; const MWWorld::TimeStamp& start = iter->second.mTimeStamp;
float magnitude = iter->second.second;
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin()); int i = 0;
iter!=effects.first.mList.end(); ++iter) for (std::vector<ESM::ENAMstruct>::const_iterator effectIter (effects.first.mList.begin());
effectIter!=effects.first.mList.end(); ++effectIter, ++i)
{ {
if (iter->mDuration) float magnitude = iter->second.mRandom[i];
if (effectIter->mRange != iter->second.mRange)
continue;
if (effectIter->mDuration)
{ {
int duration = iter->mDuration; int duration = effectIter->mDuration;
if (effects.second.first) if (effects.second.first)
duration *= magnitude; duration *= magnitude;
@ -89,9 +94,9 @@ namespace MWMechanics
{ {
const ESM::MagicEffect *magicEffect = const ESM::MagicEffect *magicEffect =
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find ( MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
iter->mEffectID); effectIter->mEffectID);
if (iter->mDuration==0) if (effectIter->mDuration==0)
{ {
param.mMagnitude = param.mMagnitude =
static_cast<int> (magnitude / (0.1 * magicEffect->mData.mBaseCost)); static_cast<int> (magnitude / (0.1 * magicEffect->mData.mBaseCost));
@ -104,9 +109,9 @@ namespace MWMechanics
} }
else else
param.mMagnitude = static_cast<int> ( param.mMagnitude = static_cast<int> (
(iter->mMagnMax-iter->mMagnMin)*magnitude + iter->mMagnMin); (effectIter->mMagnMax-effectIter->mMagnMin)*magnitude + effectIter->mMagnMin);
mEffects.add (*iter, param); mEffects.add (*effectIter, param);
} }
} }
} }
@ -115,6 +120,10 @@ namespace MWMechanics
std::pair<ESM::EffectList, std::pair<bool, bool> > ActiveSpells::getEffectList (const std::string& id) const std::pair<ESM::EffectList, std::pair<bool, bool> > ActiveSpells::getEffectList (const std::string& id) const
{ {
if (const ESM::Enchantment* enchantment =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().search (id))
return std::make_pair (enchantment->mEffects, std::make_pair(false, false));
if (const ESM::Spell *spell = if (const ESM::Spell *spell =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id)) MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
return std::make_pair (spell->mEffects, std::make_pair(false, false)); return std::make_pair (spell->mEffects, std::make_pair(false, false));
@ -156,7 +165,7 @@ namespace MWMechanics
: mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp())
{} {}
bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor) bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range, const std::string& name)
{ {
std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (id); std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (id);
bool stacks = effects.second.second; bool stacks = effects.second.second;
@ -196,11 +205,41 @@ namespace MWMechanics
random *= 0.25 * x; random *= 0.25 * x;
} }
ActiveSpellParams params;
for (unsigned int i=0; i<effects.first.mList.size(); ++i)
params.mRandom.push_back(static_cast<float> (std::rand()) / RAND_MAX);
params.mRange = range;
params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp();
params.mName = name;
if (iter==mSpells.end() || stacks) if (iter==mSpells.end() || stacks)
mSpells.insert (std::make_pair (id, mSpells.insert (std::make_pair (id, params));
std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random)));
else else
iter->second = std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random); iter->second = params;
// Play sounds
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
iter!=effects.first.mList.end(); ++iter)
{
if (iter->mRange != range)
continue;
const ESM::MagicEffect *magicEffect =
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
iter->mEffectID);
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
if(!magicEffect->mHitSound.empty())
sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f);
else
sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
break;
}
mSpellsChanged = true; mSpellsChanged = true;
@ -249,13 +288,14 @@ namespace MWMechanics
duration = iter->mDuration; duration = iter->mDuration;
} }
if (effects.second.first) // Scale duration by magnitude if needed
duration *= iterator->second.second; if (effects.second.first && iterator->second.mRandom.size())
duration *= iterator->second.mRandom.front();
double scaledDuration = duration * double scaledDuration = duration *
MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp()-iterator->second.first; double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp()-iterator->second.mTimeStamp;
if (usedUp>=scaledDuration) if (usedUp>=scaledDuration)
return 0; return 0;

View file

@ -9,6 +9,8 @@
#include "magiceffects.hpp" #include "magiceffects.hpp"
#include <components/esm/defs.hpp>
namespace ESM namespace ESM
{ {
struct Spell; struct Spell;
@ -22,6 +24,22 @@ namespace MWWorld
namespace MWMechanics namespace MWMechanics
{ {
struct ActiveSpellParams
{
// Only apply effects of this range type
ESM::RangeType mRange;
// When the spell was added
MWWorld::TimeStamp mTimeStamp;
// Random factor for each effect
std::vector<float> mRandom;
// Display name, we need this for enchantments, which don't have a name - so you need to supply the
// name of the item with the enchantment to addSpell
std::string mName;
};
/// \brief Lasting spell effects /// \brief Lasting spell effects
/// ///
/// \note The name of this class is slightly misleading, since it also handels lasting potion /// \note The name of this class is slightly misleading, since it also handels lasting potion
@ -30,7 +48,7 @@ namespace MWMechanics
{ {
public: public:
typedef std::multimap<std::string, std::pair<MWWorld::TimeStamp, float> > TContainer; typedef std::multimap<std::string, ActiveSpellParams > TContainer;
typedef TContainer::const_iterator TIterator; typedef TContainer::const_iterator TIterator;
private: private:
@ -51,9 +69,13 @@ namespace MWMechanics
ActiveSpells(); ActiveSpells();
bool addSpell (const std::string& id, const MWWorld::Ptr& actor); bool addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range = ESM::RT_Self, const std::string& name = "");
///< Overwrites an existing spell with the same ID. If the spell does not have any ///< Overwrites an existing spell with the same ID. If the spell does not have any
/// non-instant effects, it is ignored. /// non-instant effects, it is ignored.
/// @param id
/// @param actor
/// @param range Only effects with range type \a range will be applied
/// @param name Display name for enchantments, since they don't have a name in their record
/// ///
/// \return Has the spell been added? /// \return Has the spell been added?

View file

@ -273,7 +273,7 @@ void MWMechanics::Alchemy::addPotion (const std::string& name)
newRecord.mName = name; newRecord.mName = name;
int index = static_cast<int> (std::rand()/static_cast<double> (RAND_MAX)*6); int index = static_cast<int> (std::rand()/static_cast<double> (RAND_MAX+1)*6);
assert (index>=0 && index<6); assert (index>=0 && index<6);
static const char *name[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; static const char *name[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" };

View file

@ -531,6 +531,11 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
else else
sndMgr->playSound3D(mPtr, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); sndMgr->playSound3D(mPtr, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f);
} }
if (inv.getSelectedEnchantItem() != inv.end())
{
// Enchanted items cast immediately (no animation)
MWBase::Environment::get().getWorld()->castSpell(mPtr);
}
} }
else if(mWeaponType == WeapType_PickProbe) else if(mWeaponType == WeapType_PickProbe)
{ {

View file

@ -653,6 +653,9 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co
MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Thrust); MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Thrust);
else if(evt.compare(off, len, "hit") == 0) else if(evt.compare(off, len, "hit") == 0)
MWWorld::Class::get(mPtr).hit(mPtr); MWWorld::Class::get(mPtr).hit(mPtr);
else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release")
MWBase::Environment::get().getWorld()->castSpell(mPtr);
} }

View file

@ -19,6 +19,8 @@
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/movement.hpp" #include "../mwmechanics/movement.hpp"
#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/spellsuccess.hpp"
#include "../mwrender/sky.hpp" #include "../mwrender/sky.hpp"
#include "../mwrender/animation.hpp" #include "../mwrender/animation.hpp"
@ -1983,4 +1985,75 @@ namespace MWWorld
return mGodMode; return mGodMode;
} }
void World::castSpell(const Ptr &actor)
{
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
stats.setAttackingOrSpell(false);
std::string selectedSpell = stats.getSpells().getSelectedSpell();
if (!selectedSpell.empty())
{
const ESM::Spell* spell = getStore().get<ESM::Spell>().search(selectedSpell);
// Check mana
MWMechanics::DynamicStat<float> magicka = stats.getMagicka();
if (magicka.getCurrent() < spell->mData.mCost)
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}");
return;
}
// Reduce mana
magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost);
stats.setMagicka(magicka);
// Check success
int successChance = MWMechanics::getSpellSuccessChance(selectedSpell, actor);
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
if (roll >= successChance)
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}");
// TODO sound: "Spell Failure <School>"
return;
}
actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, ESM::RT_Self);
// TODO: RT_Range, RT_Touch
return;
}
InventoryStore& inv = actor.getClass().getInventoryStore(actor);
if (inv.getSelectedEnchantItem() != inv.end())
{
MWWorld::Ptr item = *inv.getSelectedEnchantItem();
std::string id = item.getClass().getEnchantment(item);
const ESM::Enchantment* enchantment = getStore().get<ESM::Enchantment>().search (id);
// Check if there's enough charge left
const float enchantCost = enchantment->mData.mCost;
MWMechanics::NpcStats &stats = MWWorld::Class::get(actor).getNpcStats(actor);
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10);
if (item.getCellRef().mEnchantmentCharge == -1)
item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge;
if (item.getCellRef().mEnchantmentCharge < castCost)
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
return;
}
// Reduce charge
item.getCellRef().mEnchantmentCharge -= castCost;
std::string itemName = item.getClass().getName(item);
actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(id, actor, ESM::RT_Self, itemName);
// TODO: RT_Range, RT_Touch
}
}
} }

View file

@ -453,6 +453,8 @@ namespace MWWorld
virtual bool getGodModeState(); virtual bool getGodModeState();
virtual bool toggleGodMode(); virtual bool toggleGodMode();
virtual void castSpell (const MWWorld::Ptr& actor);
}; };
} }