diff --git a/CHANGELOG.md b/CHANGELOG.md index e8182e5be..7ecfc07ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,7 @@ Feature #4859: Make water reflections more configurable Feature #4887: Add openmw command option to set initial random seed Feature #4890: Make Distant Terrain configurable + Feature #4962: Add casting animations for magic items Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4695: Optimize Distant Terrain memory consumption diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2fa0404d5..8dd5c4297 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1195,7 +1195,7 @@ bool CharacterController::updateCreatureState() if (!spellid.empty() && canCast) { MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell); - cast.playSpellCastingEffects(spellid); + cast.playSpellCastingEffects(spellid, false); if (!mAnimation->hasAnimation("spellcast")) { @@ -1515,23 +1515,53 @@ bool CharacterController::updateWeaponState(CharacterState& idle) stats.getSpells().setSelectedSpell(selectedSpell); } std::string spellid = stats.getSpells().getSelectedSpell(); + bool isMagicItem = false; bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr); - if(!spellid.empty() && canCast) + if (spellid.empty()) + { + if (mPtr.getClass().hasInventoryStore(mPtr)) + { + MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + if (inv.getSelectedEnchantItem() != inv.end()) + { + const MWWorld::Ptr& enchantItem = *inv.getSelectedEnchantItem(); + spellid = enchantItem.getClass().getEnchantment(enchantItem); + isMagicItem = true; + } + } + } + + static const bool useCastingAnimations = Settings::Manager::getBool("use magic item animations", "Game"); + if (isMagicItem && !useCastingAnimations) + { + // Enchanted items by default do not use casting animations + MWBase::Environment::get().getWorld()->castSpell(mPtr); + resetIdle = false; + } + else if(!spellid.empty() && canCast) { MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell); - cast.playSpellCastingEffects(spellid); + cast.playSpellCastingEffects(spellid, isMagicItem); + std::vector effects; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Spell *spell = store.get().find(spellid); - const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back(); - const ESM::MagicEffect *effect; + if (isMagicItem) + { + const ESM::Enchantment *enchantment = store.get().find(spellid); + effects = enchantment->mEffects.mList; + } + else + { + const ESM::Spell *spell = store.get().find(spellid); + effects = spell->mEffects.mList; + } - effect = store.get().find(lastEffect.mEffectID); // use last effect of list for color of VFX_Hands + const ESM::MagicEffect *effect = store.get().find(effects.back().mEffectID); // use last effect of list for color of VFX_Hands const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); - for (size_t iter = 0; iter < spell->mEffects.mList.size(); ++iter) // play hands vfx for each effect + for (size_t iter = 0; iter < effects.size(); ++iter) // play hands vfx for each effect { if (mAnimation->getNode("Bip01 L Hand")) mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); @@ -1540,7 +1570,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); } - const ESM::ENAMstruct &firstEffect = spell->mEffects.mList.at(0); // first effect used for casting animation + const ESM::ENAMstruct &firstEffect = effects.at(0); // first effect used for casting animation std::string startKey; std::string stopKey; @@ -1574,17 +1604,6 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { resetIdle = false; } - - if (mPtr.getClass().hasInventoryStore(mPtr)) - { - MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - 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/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 7b406cf9a..64a40226d 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -972,7 +972,7 @@ namespace MWMechanics // A non-actor doesn't play its spell cast effects from a character controller, so play them here if (!mCaster.getClass().isActor()) - playSpellCastingEffects(mId); + playSpellCastingEffects(mId, false); inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self); @@ -1047,15 +1047,27 @@ namespace MWMechanics return true; } - void CastSpell::playSpellCastingEffects(const std::string &spellid) + void CastSpell::playSpellCastingEffects(const std::string &spellid, bool enchantment) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Spell *spell = store.get().find(spellid); + if (enchantment) + { + const ESM::Enchantment *spell = store.get().find(spellid); + playSpellCastingEffects(spell->mEffects.mList); - std::vector addedEffects; + } + else + { + const ESM::Spell *spell = store.get().find(spellid); + playSpellCastingEffects(spell->mEffects.mList); + } + } - for (std::vector::const_iterator iter = spell->mEffects.mList.begin(); - iter != spell->mEffects.mList.end(); ++iter) + void CastSpell::playSpellCastingEffects(const std::vector& effects) + { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + std::vector addedEffects; + for (std::vector::const_iterator iter = effects.begin(); iter != effects.end(); ++iter) { const ESM::MagicEffect *effect; effect = store.get().find(iter->mEffectID); diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 5ac406368..8c4b50fe2 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -83,6 +83,9 @@ namespace MWMechanics private: MWWorld::Ptr mCaster; // May be empty MWWorld::Ptr mTarget; // May be empty + + void playSpellCastingEffects(const std::vector& effects); + public: bool mStack; std::string mId; // ID of spell, potion, item etc @@ -109,7 +112,7 @@ namespace MWMechanics /// @note Auto detects if spell, ingredient or potion bool cast (const std::string& id); - void playSpellCastingEffects(const std::string &spellid); + void playSpellCastingEffects(const std::string &spellid, bool enchantment); bool spellIncreasesSkill(); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index a10dc00d2..d73fbbe35 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1098,7 +1098,7 @@ namespace MWScript MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false); MWMechanics::CastSpell cast(ptr, target, false, true); - cast.playSpellCastingEffects(spell->mId); + cast.playSpellCastingEffects(spell->mId, false); cast.mHitPosition = target.getRefData().getPosition().asVec3(); cast.mAlwaysSucceed = true; cast.cast(spell); diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 2497b9e2f..a27399b22 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -126,6 +126,18 @@ This is how Morrowind behaves. This setting can be toggled in Advanced tab of the launcher. +use magic item animations +------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +If this setting is true, the engine will use casting animations for magic items, including scrolls. +Otherwise, there will be no casting animations, just as in original engine + +This setting can only be configured by editing the settings configuration file. + show effect duration -------------------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 353a3ac3d..c126da3ed 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -261,6 +261,9 @@ weapon sheathing = false # Allow non-standard ammunition solely to bypass normal weapon resistance or weakness only appropriate ammunition bypasses resistance = false +# Use casting animations for magic items, just as for spells +use magic item animations = false + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).