diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index a54b648974..8ed6bdca8f 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -25,6 +25,7 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "../mwrender/animation.hpp" #include "../mwmechanics/actorutil.hpp" @@ -112,6 +113,20 @@ namespace MWClass bool hasKey = false; std::string keyName; + // make door glow if player activates it with telekinesis + if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr() && + MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > + MWBase::Environment::get().getWorld()->getMaxActivationDistance()) + { + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); + + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + int index = ESM::MagicEffect::effectStringToId("sEffectTelekinesis"); + const ESM::MagicEffect *effect = store.get().find(index); + + animation->addSpellCastGlow(effect); // TODO: Telekinesis glow should only be as long as the door animation + } + // make key id lowercase std::string keyId = ptr.getCellRef().getKey(); Misc::StringUtils::lowerCaseInPlace(keyId); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 07aba2f7d0..e651955315 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -44,6 +44,7 @@ #include "creaturestats.hpp" #include "security.hpp" #include "actorutil.hpp" +#include "spellcasting.hpp" namespace { @@ -961,34 +962,6 @@ void CharacterController::updateIdleStormState(bool inwater) } } -void CharacterController::castSpell(const std::string &spellid) -{ - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Spell *spell = store.get().find(spellid); - const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); - - const ESM::MagicEffect *effect; - effect = store.get().find(effectentry.mEffectID); - - const ESM::Static* castStatic; - if (!effect->mCasting.empty()) - castStatic = store.get().find (effect->mCasting); - else - castStatic = store.get().find ("VFX_DefaultCast"); - - mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!effect->mCastSound.empty()) - sndMgr->playSound3D(mPtr, effect->mCastSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(mPtr, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); -} - bool CharacterController::updateCreatureState() { const MWWorld::Class &cls = mPtr.getClass(); @@ -1020,7 +993,8 @@ bool CharacterController::updateCreatureState() const std::string spellid = stats.getSpells().getSelectedSpell(); if (!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) { - castSpell(spellid); + MWMechanics::CastSpell cast(mPtr, NULL); + cast.playSpellCastingEffects(spellid); if (!mAnimation->hasAnimation("spellcast")) MWBase::Environment::get().getWorld()->castSpell(mPtr); // No "release" text key to use, so cast immediately @@ -1248,7 +1222,8 @@ bool CharacterController::updateWeaponState() if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) { - castSpell(spellid); + MWMechanics::CastSpell cast(mPtr, NULL); + cast.playSpellCastingEffects(spellid); const ESM::Spell *spell = store.get().find(spellid); const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 190e171b3c..d5dc5fe284 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -214,8 +214,6 @@ class CharacterController : public MWRender::Animation::TextKeyListener void updateHeadTracking(float duration); - void castSpell(const std::string& spellid); - void updateMagicEffects(); void playDeath(float startpoint, CharacterState death); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 52815816ed..c55ae67f28 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -574,6 +574,10 @@ namespace MWMechanics { if (effectId == ESM::MagicEffect::Lock) { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::MagicEffect *magiceffect = store.get().find(effectId); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); + animation->addSpellCastGlow(magiceffect); if (target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude { if (caster == getPlayer()) @@ -584,6 +588,10 @@ namespace MWMechanics } else if (effectId == ESM::MagicEffect::Open) { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::MagicEffect *magiceffect = store.get().find(effectId); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); + animation->addSpellCastGlow(magiceffect); if (target.getCellRef().getLockLevel() <= magnitude) { if (target.getCellRef().getLockLevel() > 0) @@ -836,6 +844,10 @@ namespace MWMechanics if (mCaster == getPlayer() && spellIncreasesSkill(spell)) mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0); + + // A non-actor doesn't play its spell cast effects from a character controller, so play them here + if (!mCaster.getClass().isActor()) + playSpellCastingEffects(mId); inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self); @@ -930,6 +942,42 @@ namespace MWMechanics return true; } + void CastSpell::playSpellCastingEffects(const std::string &spellid){ + + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Spell *spell = store.get().find(spellid); + const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); + + const ESM::MagicEffect *effect; + effect = store.get().find(effectentry.mEffectID); + + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); + + if (mCaster.getClass().isActor()) // TODO: Non-actors (except for large statics?) should also create a spell cast vfx + { + const ESM::Static* castStatic; + if (!effect->mCasting.empty()) + castStatic = store.get().find (effect->mCasting); + else + castStatic = store.get().find ("VFX_DefaultCast"); + + animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); + } + + if (!mCaster.getClass().isActor()) + animation->addSpellCastGlow(effect); + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!effect->mCastSound.empty()) + sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); + } + int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor) { /* diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index ae20a39d01..aba263b010 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -92,6 +92,8 @@ namespace MWMechanics /// @note Auto detects if spell, ingredient or potion bool cast (const std::string& id); + void playSpellCastingEffects(const std::string &spellid); + /// @note \a target can be any type of object, not just actors. /// @note \a caster can be any type of object, or even an empty object. void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3f09dffc54..e5614f3f8f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -86,48 +86,6 @@ namespace std::vector > mToRemove; }; - class GlowUpdater : public SceneUtil::StateSetUpdater - { - public: - GlowUpdater(int texUnit, osg::Vec4f color, const std::vector >& textures) - : mTexUnit(texUnit) - , mColor(color) - , mTextures(textures) - { - } - - virtual void setDefaults(osg::StateSet *stateset) - { - stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON); - - osg::TexGen* texGen = new osg::TexGen; - texGen->setMode(osg::TexGen::SPHERE_MAP); - - stateset->setTextureAttributeAndModes(mTexUnit, texGen, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; - texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); - texEnv->setConstantColor(mColor); - texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); - texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); - texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR); - - stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON); - } - - virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) - { - float time = nv->getFrameStamp()->getSimulationTime(); - int index = (int)(time*16) % mTextures.size(); - stateset->setTextureAttribute(mTexUnit, mTextures[index], osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - - private: - int mTexUnit; - osg::Vec4f mColor; - std::vector > mTextures; - }; - class NodeMapVisitor : public osg::NodeVisitor { public: @@ -289,6 +247,134 @@ namespace namespace MWRender { + class GlowUpdater : public SceneUtil::StateSetUpdater + { + public: + GlowUpdater(int texUnit, osg::Vec4f color, const std::vector >& textures, + osg::Node* node, float duration, Resource::ResourceSystem* resourcesystem) + : mTexUnit(texUnit) + , mColor(color) + , mOriginalColor(color) + , mTextures(textures) + , mNode(node) + , mDuration(duration) + , mOriginalDuration(duration) + , mStartingTime(0) + , mResourceSystem(resourcesystem) + , mColorChanged(false) + , mDone(false) + { + } + + virtual void setDefaults(osg::StateSet *stateset) + { + if (mDone) + removeTexture(stateset); + else + { + stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON); + osg::TexGen* texGen = new osg::TexGen; + texGen->setMode(osg::TexGen::SPHERE_MAP); + + stateset->setTextureAttributeAndModes(mTexUnit, texGen, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); + texEnv->setConstantColor(mColor); + texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); + texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); + texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR); + + stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("envMapColor", mColor)); + } + } + + void removeTexture(osg::StateSet* stateset) + { + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXTURE); + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXGEN); + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXENV); + stateset->removeTextureMode(mTexUnit, GL_TEXTURE_2D); + stateset->removeUniform("envMapColor"); + + osg::StateSet::TextureAttributeList& list = stateset->getTextureAttributeList(); + while (list.size() && list.rbegin()->empty()) + list.pop_back(); + } + + virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) + { + if (mColorChanged){ + this->reset(); + setDefaults(stateset); + mColorChanged = false; + } + if (mDone) + return; + + // Set the starting time to measure glow duration from if this is a temporary glow + if ((mDuration >= 0) && mStartingTime == 0) + mStartingTime = nv->getFrameStamp()->getSimulationTime(); + + float time = nv->getFrameStamp()->getSimulationTime(); + int index = (int)(time*16) % mTextures.size(); + stateset->setTextureAttribute(mTexUnit, mTextures[index], osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + if ((mDuration >= 0) && (time - mStartingTime > mDuration)) // If this is a temporary glow and it has finished its duration + { + if (mOriginalDuration >= 0) // if this glowupdater was a temporary glow since its creation + { + removeTexture(stateset); + this->reset(); + mDone = true; + mResourceSystem->getSceneManager()->recreateShaders(mNode); + } + if (mOriginalDuration < 0) // if this glowupdater was originally a permanent glow + { + mDuration = mOriginalDuration; + mStartingTime = 0; + mColor = mOriginalColor; + this->reset(); + setDefaults(stateset); + } + } + } + + bool isPermanentGlowUpdater() + { + return (mDuration < 0); + } + + bool isDone() + { + return mDone; + } + + void setColor(osg::Vec4f color) + { + mColor = color; + mColorChanged = true; + } + + void setDuration(float duration) + { + mDuration = duration; + } + + private: + int mTexUnit; + osg::Vec4f mColor; + osg::Vec4f mOriginalColor; // for restoring the color of a permanent glow after a temporary glow on the object finishes + std::vector > mTextures; + osg::Node* mNode; + float mDuration; + float mOriginalDuration; // for recording that this is originally a permanent glow if it is changed to a temporary one + float mStartingTime; + Resource::ResourceSystem* mResourceSystem; + bool mColorChanged; + bool mDone; + }; struct Animation::AnimSource { @@ -1106,7 +1192,29 @@ namespace MWRender int mLowestUnusedTexUnit; }; - void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor) + void Animation::addSpellCastGlow(const ESM::MagicEffect *effect) + { + osg::Vec4f glowColor(1,1,1,1); + glowColor.x() = effect->mData.mRed / 255.f; + glowColor.y() = effect->mData.mGreen / 255.f; + glowColor.z() = effect->mData.mBlue / 255.f; + + if (!mGlowUpdater || (mGlowUpdater->isDone() || (mGlowUpdater->isPermanentGlowUpdater() == true))) + { + if (mGlowUpdater && mGlowUpdater->isDone()) + mObjectRoot->removeUpdateCallback(mGlowUpdater); + + if (mGlowUpdater && mGlowUpdater->isPermanentGlowUpdater()) + { + mGlowUpdater->setColor(glowColor); + mGlowUpdater->setDuration(1.5); // Glow length measured from original engine as about 1.5 seconds + } + else + addGlow(mObjectRoot, glowColor, 1.5); + } + } + + void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor, float glowDuration) { std::vector > textures; for (int i=0; i<32; ++i) @@ -1130,9 +1238,11 @@ namespace MWRender FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; node->accept(findLowestUnusedTexUnitVisitor); int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; - osg::ref_ptr glowupdater (new GlowUpdater(texUnit, glowColor, textures)); - node->addUpdateCallback(glowupdater); + osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, mResourceSystem); + mGlowUpdater = glowUpdater; + node->addUpdateCallback(glowUpdater); + // set a texture now so that the ShaderVisitor can find it osg::ref_ptr writableStateSet = NULL; if (!node->getStateSet()) @@ -1144,7 +1254,6 @@ namespace MWRender } writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); - mResourceSystem->getSceneManager()->recreateShaders(node); } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index ad9d4ab4ab..a837a26aeb 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -8,6 +8,7 @@ namespace ESM { struct Light; + struct MagicEffect; } namespace Resource @@ -32,6 +33,7 @@ namespace MWRender class ResetAccumRootCallback; class RotateController; +class GlowUpdater; class EffectAnimationTime : public SceneUtil::ControllerSource { @@ -261,6 +263,7 @@ protected: float mHeadPitchRadians; osg::ref_ptr mGlowLight; + osg::ref_ptr mGlowUpdater; float mAlpha; @@ -317,7 +320,7 @@ protected: osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const; - void addGlow(osg::ref_ptr node, osg::Vec4f glowColor); + void addGlow(osg::ref_ptr node, osg::Vec4f glowColor, float glowDuration = -1); /// Set the render bin for this animation's object root. May be customized by subclasses. virtual void setRenderBin(); @@ -351,6 +354,8 @@ public: void removeEffect (int effectId); void getLoopingEffects (std::vector& out) const; + void addSpellCastGlow(const ESM::MagicEffect *effect); + virtual void updatePtr(const MWWorld::Ptr &ptr); bool hasAnimation(const std::string &anim) const;