1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-22 22:40:12 +00:00

Merge pull request #1001 from Allofich/traps

Make non-actors play spell cast visuals and sounds
This commit is contained in:
scrawl 2016-08-09 20:17:31 +02:00 committed by GitHub
commit 6b45a757a9
7 changed files with 231 additions and 79 deletions

View file

@ -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<ESM::MagicEffect>().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);

View file

@ -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<ESM::Spell>().find(spellid);
const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0);
const ESM::MagicEffect *effect;
effect = store.get<ESM::MagicEffect>().find(effectentry.mEffectID);
const ESM::Static* castStatic;
if (!effect->mCasting.empty())
castStatic = store.get<ESM::Static>().find (effect->mCasting);
else
castStatic = store.get<ESM::Static>().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<ESM::Spell>().find(spellid);
const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0);

View file

@ -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);

View file

@ -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<ESM::MagicEffect>().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<ESM::MagicEffect>().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<ESM::Spell>().find(spellid);
const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0);
const ESM::MagicEffect *effect;
effect = store.get<ESM::MagicEffect>().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<ESM::Static>().find (effect->mCasting);
else
castStatic = store.get<ESM::Static>().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)
{
/*

View file

@ -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,

View file

@ -86,48 +86,6 @@ namespace
std::vector<osg::ref_ptr<osg::Node> > mToRemove;
};
class GlowUpdater : public SceneUtil::StateSetUpdater
{
public:
GlowUpdater(int texUnit, osg::Vec4f color, const std::vector<osg::ref_ptr<osg::Texture2D> >& 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<osg::ref_ptr<osg::Texture2D> > 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<osg::ref_ptr<osg::Texture2D> >& 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<osg::ref_ptr<osg::Texture2D> > 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<osg::Node> 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<osg::Node> node, osg::Vec4f glowColor, float glowDuration)
{
std::vector<osg::ref_ptr<osg::Texture2D> > 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> glowupdater (new GlowUpdater(texUnit, glowColor, textures));
node->addUpdateCallback(glowupdater);
osg::ref_ptr<GlowUpdater> 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<osg::StateSet> 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);
}

View file

@ -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<SceneUtil::LightSource> mGlowLight;
osg::ref_ptr<GlowUpdater> mGlowUpdater;
float mAlpha;
@ -317,7 +320,7 @@ protected:
osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const;
void addGlow(osg::ref_ptr<osg::Node> node, osg::Vec4f glowColor);
void addGlow(osg::ref_ptr<osg::Node> 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<int>& out) const;
void addSpellCastGlow(const ESM::MagicEffect *effect);
virtual void updatePtr(const MWWorld::Ptr &ptr);
bool hasAnimation(const std::string &anim) const;