diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c39e878268..8ae563e124 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -406,6 +406,8 @@ namespace MWBase virtual bool getGodModeState() = 0; virtual bool toggleGodMode() = 0; + + virtual void castSpell (const MWWorld::Ptr& actor) = 0; }; } diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 0c303485af..5d002792a2 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -97,6 +97,8 @@ namespace MWGui } // add lasting effect spells/potions etc + + // TODO: Move this to ActiveSpells const MWMechanics::ActiveSpells::TContainer& activeSpells = stats.getActiveSpells().getActiveSpells(); for (MWMechanics::ActiveSpells::TContainer::const_iterator it = activeSpells.begin(); it != activeSpells.end(); ++it) @@ -105,31 +107,36 @@ namespace MWGui float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + int i=0; for (std::vector::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 = MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); MagicEffectInfo effectInfo; - effectInfo.mSource = getSpellDisplayName (it->first); + effectInfo.mSource = it->second.mName; effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) effectInfo.mKey.mArg = effectIt->mSkill; else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) 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 + - (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 - /// \todo duplicated from ActiveSpells, helper function? if (MWBase::Environment::get().getWorld()->getStore().get().search (it->first)) { - effectInfo.mRemainingTime = effectIt->mDuration * it->second.second + - (it->second.first - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; + effectInfo.mRemainingTime = effectIt->mDuration * randomFactor + + (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; - effectInfo.mMagnitude = static_cast (0.05*it->second.second / (0.1 * magicEffect->mData.mBaseCost)); + effectInfo.mMagnitude = static_cast (0.05*randomFactor / (0.1 * magicEffect->mData.mBaseCost)); } effects[effectIt->mEffectID].push_back (effectInfo); @@ -288,6 +295,10 @@ namespace MWGui ESM::EffectList SpellIcons::getSpellEffectList (const std::string& id) { + if (const ESM::Enchantment* enchantment = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return enchantment->mEffects; + if (const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().search (id)) return spell->mEffects; diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 3a609aa91f..85c71575b6 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -467,7 +467,7 @@ namespace MWGui } Widgets::MWDynamicStatPtr chargeWidget = enchantArea->createWidget ("MW_ChargeBar", chargeCoord, MyGUI::Align::Default, "ToolTipEnchantCharge"); - chargeWidget->setValue(charge, charge); + chargeWidget->setValue(charge, maxCharge); totalSize.height += 24; } } diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 9aca6b7b79..c433ac5e78 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -15,6 +15,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" @@ -64,15 +65,19 @@ namespace MWMechanics { std::pair > effects = getEffectList (iter->first); - const MWWorld::TimeStamp& start = iter->second.first; - float magnitude = iter->second.second; + const MWWorld::TimeStamp& start = iter->second.mTimeStamp; - for (std::vector::const_iterator iter (effects.first.mList.begin()); - iter!=effects.first.mList.end(); ++iter) + int i = 0; + for (std::vector::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) duration *= magnitude; @@ -89,9 +94,9 @@ namespace MWMechanics { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( - iter->mEffectID); + effectIter->mEffectID); - if (iter->mDuration==0) + if (effectIter->mDuration==0) { param.mMagnitude = static_cast (magnitude / (0.1 * magicEffect->mData.mBaseCost)); @@ -104,9 +109,9 @@ namespace MWMechanics } else param.mMagnitude = static_cast ( - (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 > ActiveSpells::getEffectList (const std::string& id) const { + if (const ESM::Enchantment* enchantment = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return std::make_pair (enchantment->mEffects, std::make_pair(false, false)); + if (const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().search (id)) 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()) {} - 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 > effects = getEffectList (id); bool stacks = effects.second.second; @@ -196,11 +205,41 @@ namespace MWMechanics random *= 0.25 * x; } + ActiveSpellParams params; + for (unsigned int i=0; i (std::rand()) / RAND_MAX); + params.mRange = range; + params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); + params.mName = name; + if (iter==mSpells.end() || stacks) - mSpells.insert (std::make_pair (id, - std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random))); + mSpells.insert (std::make_pair (id, params)); else - iter->second = std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random); + iter->second = params; + + // Play sounds + for (std::vector::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().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; @@ -249,13 +288,14 @@ namespace MWMechanics duration = iter->mDuration; } - if (effects.second.first) - duration *= iterator->second.second; + // Scale duration by magnitude if needed + if (effects.second.first && iterator->second.mRandom.size()) + duration *= iterator->second.mRandom.front(); double scaledDuration = duration * 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) return 0; diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 8c859b2cb3..e3f882b9aa 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -9,6 +9,8 @@ #include "magiceffects.hpp" +#include + namespace ESM { struct Spell; @@ -22,6 +24,22 @@ namespace MWWorld 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 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 /// /// \note The name of this class is slightly misleading, since it also handels lasting potion @@ -30,7 +48,7 @@ namespace MWMechanics { public: - typedef std::multimap > TContainer; + typedef std::multimap TContainer; typedef TContainer::const_iterator TIterator; private: @@ -51,9 +69,13 @@ namespace MWMechanics 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 /// 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? diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 1d992be413..adfd6d5fce 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -273,7 +273,7 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) newRecord.mName = name; - int index = static_cast (std::rand()/static_cast (RAND_MAX)*6); + int index = static_cast (std::rand()/static_cast (RAND_MAX+1)*6); assert (index>=0 && index<6); static const char *name[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c4260d907d..8c88b3ad03 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -531,6 +531,11 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun else 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) { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 545060fe3a..53d697fdd9 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -653,6 +653,9 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Thrust); else if(evt.compare(off, len, "hit") == 0) MWWorld::Class::get(mPtr).hit(mPtr); + + else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release") + MWBase::Environment::get().getWorld()->castSpell(mPtr); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 6a4a380d65..f9d3d8d1e8 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -26,6 +26,8 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellsuccess.hpp" + #include "../mwrender/sky.hpp" #include "../mwrender/animation.hpp" @@ -2019,4 +2021,115 @@ namespace MWWorld } } } + + 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().search(selectedSpell); + + // Check mana + bool fail = false; + MWMechanics::DynamicStat magicka = stats.getMagicka(); + if (magicka.getCurrent() < spell->mData.mCost) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}"); + fail = true; + } + + // Reduce mana + if (!fail) + { + magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); + stats.setMagicka(magicka); + } + + // Check success + int successChance = MWMechanics::getSpellSuccessChance(selectedSpell, actor); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (!fail && roll >= successChance) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); + fail = true; + } + + if (fail) + { + // Failure sound + for (std::vector::const_iterator iter (spell->mEffects.mList.begin()); + iter!=spell->mEffects.mList.end(); ++iter) + { + const ESM::MagicEffect *magicEffect = getStore().get().find ( + iter->mEffectID); + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(actor, "Spell Failure " + schools[magicEffect->mData.mSchool], 1.0f, 1.0f); + break; + } + 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().search (id); + + + if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) + { + // 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; + } + if (enchantment->mData.mType == ESM::Enchantment::CastOnce) + { + item.getRefData().setCount(item.getRefData().getCount()-1); + } + + std::string itemName = item.getClass().getName(item); + actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(id, actor, ESM::RT_Self, itemName); + + if (!item.getRefData().getCount()) + { + // Item was used up + MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); + inv.setSelectedEnchantItem(inv.end()); + } + else + MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge + + + // TODO: RT_Range, RT_Touch + } + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index aadf7ce981..49f26998de 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -466,6 +466,8 @@ namespace MWWorld virtual bool getGodModeState(); virtual bool toggleGodMode(); + + virtual void castSpell (const MWWorld::Ptr& actor); }; }