diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index e351229d0..fb1662a4a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1235,10 +1235,8 @@ bool CharacterController::updateWeaponState() MWMechanics::CastSpell cast(mPtr, NULL); cast.playSpellCastingEffects(spellid); - const ESM::Spell *spell = store.get().find(spellid); - - const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.at(spell->mEffects.mList.size() - 1); - + const ESM::Spell *spell = store.get().find(spellid); + const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back(); const ESM::MagicEffect *effect; effect = store.get().find(lastEffect.mEffectID); // use last effect of list for color of VFX_Hands diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 5024dfeec..95a7e1a3b 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -30,7 +30,6 @@ namespace MWMechanics { - ESM::Skill::SkillEnum spellSchoolToSkill(int school) { std::map schoolSkillMap; // maps spell school to skill id @@ -542,12 +541,14 @@ namespace MWMechanics else castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_DefaultHit"); + std::string texture = magicEffect->mParticle; + // TODO: VFX are no longer active after saving/reloading the game bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; // Note: in case of non actor, a free effect should be fine as well MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); if (anim) - anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); + anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", texture); } } } @@ -931,15 +932,18 @@ namespace MWMechanics 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 + if (mCaster.getClass().isActor()) // TODO: Non-actors 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); + std::string texture = effect->mParticle; + + animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture); } if (!mCaster.getClass().isActor()) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 386a4a53b..709780307 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1342,7 +1342,7 @@ namespace MWRender SceneUtil::AssignControllerSourcesVisitor assignVisitor(boost::shared_ptr(params.mAnimTime)); node->accept(assignVisitor); - overrideTexture(texture, mResourceSystem, node); + overrideFirstRootTexture(texture, mResourceSystem, node); // TODO: in vanilla morrowind the effect is scaled based on the host object's bounding box. diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index e2773f2dc..340a855fb 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -25,7 +25,7 @@ EffectManager::~EffectManager() clear(); } -void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, float scale) +void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) { osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model); @@ -46,7 +46,10 @@ void EffectManager::addEffect(const std::string &model, const std::string& textu SceneUtil::AssignControllerSourcesVisitor assignVisitor(effect.mAnimTime); node->accept(assignVisitor); - overrideTexture(textureOverride, mResourceSystem, node); + if (isMagicVFX) + overrideFirstRootTexture(textureOverride, mResourceSystem, node); + else + overrideTexture(textureOverride, mResourceSystem, node); mParentNode->addChild(trans); mResourceSystem->getSceneManager()->notifyAttached(node); diff --git a/apps/openmw/mwrender/effectmanager.hpp b/apps/openmw/mwrender/effectmanager.hpp index 6d7aaaf4f..83acc4c60 100644 --- a/apps/openmw/mwrender/effectmanager.hpp +++ b/apps/openmw/mwrender/effectmanager.hpp @@ -33,7 +33,7 @@ namespace MWRender ~EffectManager(); /// Add an effect. When it's finished playing, it will be removed automatically. - void addEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPosition, float scale); + void addEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPosition, float scale, bool isMagicVFX = true); void update(float dt); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index b1f506e11..64cc6d26c 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -970,12 +970,12 @@ Resource::ResourceSystem* NpcAnimation::getResourceSystem() return mResourceSystem; } -void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) +void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew) { // During first auto equip, we don't play any sounds. // Basically we don't want sounds when the actor is first loaded, // the items should appear as if they'd always been equipped. - if (playSound) + if (isNew) { static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" @@ -994,7 +994,7 @@ void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, boo bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; // Don't play particle VFX unless the effect is new or it should be looping. if (isNew || loop) - addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); + addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle); } } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index baf9c8c24..9134f8f5f 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -23,7 +23,7 @@ class NpcAnimation : public Animation, public WeaponAnimation, public MWWorld::I { public: virtual void equipmentChanged(); - virtual void permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound); + virtual void permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew); public: typedef std::map PartBoneMap; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d74bac0b3..c237f2320 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -742,9 +742,9 @@ namespace MWRender mObjects->updatePtr(old, updated); } - void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale) + void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) { - mEffectManager->addEffect(model, texture, worldPosition, scale); + mEffectManager->addEffect(model, texture, worldPosition, scale, isMagicVFX); } void RenderingManager::notifyWorldSpaceChanged() diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index f5245be98..3be01931a 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -138,7 +138,7 @@ namespace MWRender SkyManager* getSkyManager(); - void spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale = 1.f); + void spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale = 1.f, bool isMagicVFX = true); /// Clear all savegame-specific data void clear(); diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index cb1953c61..aa8aaccbc 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -1,14 +1,47 @@ #include "util.hpp" #include +#include #include #include #include +#include namespace MWRender { +class TextureOverrideVisitor : public osg::NodeVisitor + { + public: + TextureOverrideVisitor(std::string texture, Resource::ResourceSystem* resourcesystem) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mTexture(texture) + , mResourcesystem(resourcesystem) + { + } + + virtual void apply(osg::Node& node) + { + int index; + osg::ref_ptr nodePtr(&node); + if (node.getUserValue("overrideFx", index)) + { + if (index == 1) + overrideTexture(mTexture, mResourcesystem, nodePtr); + } + traverse(node); + } + std::string mTexture; + Resource::ResourceSystem* mResourcesystem; +}; + +void overrideFirstRootTexture(const std::string &texture, Resource::ResourceSystem *resourceSystem, osg::ref_ptr node) +{ + TextureOverrideVisitor overrideVisitor(texture, resourceSystem); + node->accept(overrideVisitor); +} + void overrideTexture(const std::string &texture, Resource::ResourceSystem *resourceSystem, osg::ref_ptr node) { if (texture.empty()) diff --git a/apps/openmw/mwrender/util.hpp b/apps/openmw/mwrender/util.hpp index 815e34596..eebcfd9b0 100644 --- a/apps/openmw/mwrender/util.hpp +++ b/apps/openmw/mwrender/util.hpp @@ -17,6 +17,10 @@ namespace Resource namespace MWRender { + // Overrides the texture of nodes in the mesh that had the same NiTexturingProperty as the first NiTexturingProperty of the .NIF file's root node, + // if it had a NiTexturingProperty. Used for applying "particle textures" to magic effects. + void overrideFirstRootTexture(const std::string &texture, Resource::ResourceSystem *resourceSystem, osg::ref_ptr node); + void overrideTexture(const std::string& texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node); // Node callback to entirely skip the traversal. diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index e8a4d1c4d..2c25029da 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -414,8 +414,7 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) // During first auto equip, we don't play any sounds. // Basically we don't want sounds when the actor is first loaded, // the items should appear as if they'd always been equipped. - mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip, - !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin()); + mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip); } if (magnitude) diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 2d7c9f6e9..152176fbe 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -32,7 +32,7 @@ namespace MWWorld * If it isn't new, non-looping VFX should not be played. * @param playSound Play effect sound? */ - virtual void permanentEffectAdded (const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) {} + virtual void permanentEffectAdded (const ESM::MagicEffect *magicEffect, bool isNew) {} }; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index c70f3de99..2f8576bda 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -30,6 +30,7 @@ #include "../mwrender/animation.hpp" #include "../mwrender/vismask.hpp" #include "../mwrender/renderingmanager.hpp" +#include "../mwrender/util.hpp" #include "../mwsound/sound.hpp" @@ -37,7 +38,7 @@ namespace { - ESM::EffectList getMagicBoltData(std::vector& projectileIDs, std::vector& sounds, float& speed, const ESM::EffectList& effects) + ESM::EffectList getMagicBoltData(std::vector& projectileIDs, std::vector& sounds, float& speed, std::string& texture, const ESM::EffectList& effects) { int count = 0; speed = 0.0f; @@ -73,6 +74,14 @@ namespace if (count != 0) speed /= count; + + // the particle texture is only used if there is only one projectile + if (projectileEffects.mList.size() == 1) + { + const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( + effects.mList.begin()->mEffectID); + texture = magicEffect->mParticle; + } if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects { @@ -127,7 +136,7 @@ namespace MWWorld }; - void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate) + void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, std::string texture) { state.mNode = new osg::PositionAttitudeTransform; state.mNode->setNodeMask(MWRender::Mask_Effect); @@ -144,7 +153,7 @@ namespace MWWorld attachTo = rotateNode; } - mResourceSystem->getSceneManager()->getInstance(model, attachTo); + osg::ref_ptr projectile = mResourceSystem->getSceneManager()->getInstance(model, attachTo); if (state.mIdMagic.size() > 1) for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter) @@ -169,6 +178,8 @@ namespace MWWorld SceneUtil::AssignControllerSourcesVisitor assignVisitor (state.mEffectAnimationTime); state.mNode->accept(assignVisitor); + + MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile); } void ProjectileManager::update(State& state, float duration) @@ -207,7 +218,9 @@ namespace MWWorld state.mActorId = -1; state.mStack = stack; - state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, effects); + std::string texture = ""; + + state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, effects); // Non-projectile should have been removed by getMagicBoltData if (state.mEffects.mList.empty()) @@ -216,7 +229,7 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); - createModel(state, ptr.getClass().getModel(ptr), pos, orient, true); + createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, texture); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (size_t it = 0; it != state.mSoundIds.size(); it++) @@ -486,7 +499,8 @@ namespace MWWorld state.mSpellId = esm.mSpellId; state.mActorId = esm.mActorId; state.mStack = esm.mStack; - state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, esm.mEffects); + std::string texture = ""; + state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, esm.mEffects); state.mSpeed = esm.mSpeed; // speed is derived from non-projectile effects as well as // projectile effects, so we can't calculate it from the save // file's effect list, which is already trimmed of non-projectile @@ -504,7 +518,7 @@ namespace MWWorld return true; } - createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true); + createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, texture); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 55ce0cd12..a8769fcf9 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -122,7 +122,7 @@ namespace MWWorld void moveProjectiles(float dt); void moveMagicBolts(float dt); - void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate); + void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, std::string texture = ""); void update (State& state, float duration); void operator=(const ProjectileManager&); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 82f99b7ea..b5ef7de52 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3145,7 +3145,7 @@ namespace MWWorld modelName << roll; std::string model = "meshes\\" + getFallback()->getFallbackString(modelName.str()); - mRendering->spawnEffect(model, texture, worldPosition); + mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false); } void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos) @@ -3162,7 +3162,7 @@ namespace MWWorld { const ESM::MagicEffect* effect = getStore().get().find(effectIt->mEffectID); - if ((effectIt->mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor()) || effectIt->mRange != rangeType) + if (effectIt->mRange != rangeType || (effectIt->mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) continue; // Not right range type, or not area effect and hit an actor // Spawn the explosion orb effect @@ -3172,13 +3172,15 @@ namespace MWWorld else areaStatic = getStore().get().find ("VFX_DefaultArea"); + std::string texture = effect->mParticle; + if (effectIt->mArea <= 0) { - mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, 1.0f); + mRendering->spawnEffect("meshes\\" + areaStatic->mModel, texture, origin, 1.0f); continue; } else - mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, static_cast(effectIt->mArea * 2)); + mRendering->spawnEffect("meshes\\" + areaStatic->mModel, texture, origin, static_cast(effectIt->mArea * 2)); // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) static const std::string schools[] = { diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index c08cd8391..7823b3634 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -6,6 +6,7 @@ #include #include #include +#include // resource #include @@ -276,11 +277,13 @@ namespace NifOsg public: /// @param filename used for warning messages. LoaderImpl(const std::string& filename) - : mFilename(filename) + : mFilename(filename), mFirstRootTextureIndex(-1), mFoundFirstRootTexturingProperty(false) { } std::string mFilename; + size_t mFirstRootTextureIndex; + bool mFoundFirstRootTexturingProperty; static void loadKf(Nif::NIFFilePtr nif, KeyframeHolder& target) { @@ -371,10 +374,24 @@ namespace NifOsg void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { const Nif::PropertyList& props = nifNode->props; - for (size_t i = 0; i parent == NULL && !mFoundFirstRootTexturingProperty && props[i].getPtr()->recType == Nif::RC_NiTexturingProperty) + { + mFirstRootTextureIndex = props[i].getPtr()->recIndex; + mFoundFirstRootTexturingProperty = true; + } + else if (props[i].getPtr()->recType == Nif::RC_NiTexturingProperty) + { + if (props[i].getPtr()->recIndex == mFirstRootTextureIndex) + applyTo->setUserValue("overrideFx", 1); + } handleProperty(props[i].getPtr(), applyTo, composite, imageManager, boundTextures, animflags); + } } }