From 0cd40294a24cff680eb695c23d54021bd8be807f Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 12 Mar 2014 11:30:44 +0100 Subject: [PATCH] Fixed ranged combat for creatures --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/creatureanimation.cpp | 51 ++++++- apps/openmw/mwrender/creatureanimation.hpp | 21 ++- apps/openmw/mwrender/npcanimation.cpp | 148 +++---------------- apps/openmw/mwrender/npcanimation.hpp | 27 +--- apps/openmw/mwrender/weaponanimation.cpp | 159 +++++++++++++++++++++ apps/openmw/mwrender/weaponanimation.hpp | 56 ++++++++ 7 files changed, 307 insertions(+), 157 deletions(-) create mode 100644 apps/openmw/mwrender/weaponanimation.cpp create mode 100644 apps/openmw/mwrender/weaponanimation.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index eb502de38..5e108edaf 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows characterpreview globalmap videoplayer ripplesimulation refraction - terrainstorage renderconst effectmanager + terrainstorage renderconst effectmanager weaponanimation ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 8ef584154..0eb883953 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -55,6 +55,8 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr) updateParts(); } + + mWeaponAnimationTime = Ogre::SharedPtr(new WeaponAnimationTime(this)); } void CreatureWeaponAnimation::showWeapons(bool showWeapon) @@ -110,6 +112,20 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo setRenderProperties(scene, RV_Actors, RQG_Main, RQG_Alpha, 0, !item.getClass().getEnchantment(item).empty(), &glowColor); + // Crossbows start out with a bolt attached + if (slot == MWWorld::InventoryStore::Slot_CarriedRight && + item.getTypeName() == typeid(ESM::Weapon).name() && + item.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) + { + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) + attachArrow(); + else + mAmmunition.setNull(); + } + else + mAmmunition.setNull(); + if(scene->mSkelBase) { Ogre::SkeletonInstance *skel = scene->mSkelBase->getSkeleton(); @@ -133,15 +149,42 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo updateSkeletonInstance(mSkelBase->getSkeleton(), skel); } - // TODO: - // type == ESM::PRT_Weapon should get an animation source based on the current offset - // of the weapon attack animation (from its beginning, or start marker?) std::vector >::iterator ctrl(scene->mControllers.begin()); for(;ctrl != scene->mControllers.end();ctrl++) { if(ctrl->getSource().isNull()) - ctrl->setSource(Ogre::SharedPtr(new NullAnimationTime())); + { + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + ctrl->setSource(mWeaponAnimationTime); + else + ctrl->setSource(Ogre::SharedPtr(new NullAnimationTime())); + } } } +void CreatureWeaponAnimation::configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) +{ + Ogre::Vector3 glowColor = getEnchantmentColor(ptr); + + setRenderProperties(object, RV_Actors, RQG_Main, RQG_Alpha, 0, + !ptr.getClass().getEnchantment(ptr).empty(), &glowColor); +} + +void CreatureWeaponAnimation::attachArrow() +{ + WeaponAnimation::attachArrow(mPtr); +} + +void CreatureWeaponAnimation::releaseArrow() +{ + WeaponAnimation::releaseArrow(mPtr); +} + +Ogre::Vector3 CreatureWeaponAnimation::runAnimation(float duration) +{ + Ogre::Vector3 ret = Animation::runAnimation(duration); + pitchSkeleton(mPtr.getRefData().getPosition().rot[0], mSkelBase->getSkeleton()); + return ret; +} + } diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index 37826673d..d6cd8a517 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -2,6 +2,7 @@ #define GAME_RENDER_CREATUREANIMATION_H #include "animation.hpp" +#include "weaponanimation.hpp" #include "../mwworld/inventorystore.hpp" namespace MWWorld @@ -21,7 +22,7 @@ namespace MWRender // For creatures with weapons and shields // Animation is already virtual anyway, so might as well make a separate class. // Most creatures don't need weapons/shields, so this will save some memory. - class CreatureWeaponAnimation : public Animation, public MWWorld::InventoryStoreListener + class CreatureWeaponAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: CreatureWeaponAnimation(const MWWorld::Ptr& ptr); @@ -36,11 +37,29 @@ namespace MWRender void updatePart(NifOgre::ObjectScenePtr& scene, int slot); + virtual void attachArrow(); + virtual void releaseArrow(); + + virtual Ogre::Vector3 runAnimation(float duration); + + /// A relative factor (0-1) that decides if and how much the skeleton should be pitched + /// to indicate the facing orientation of the character. + virtual void setPitchFactor(float factor) { mPitchFactor = factor; } + + virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); } + + // WeaponAnimation + virtual NifOgre::ObjectScenePtr getWeapon() { return mWeapon; } + virtual void showWeapon(bool show) { showWeapons(show); } + virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot); + private: NifOgre::ObjectScenePtr mWeapon; NifOgre::ObjectScenePtr mShield; bool mShowWeapons; bool mShowCarriedLeft; + + Ogre::SharedPtr mWeaponAnimationTime; }; } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 8f8e65cd0..a09a58191 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -76,27 +76,6 @@ float HeadAnimationTime::getValue() const return 1; } -float WeaponAnimationTime::getValue() const -{ - if (mWeaponGroup.empty()) - return 0; - float current = mAnimation->getCurrentTime(mWeaponGroup); - if (current == -1) - return 0; - return current - mStartTime; -} - -void WeaponAnimationTime::setGroup(const std::string &group) -{ - mWeaponGroup = group; - mStartTime = mAnimation->getStartTime(mWeaponGroup); -} - -void WeaponAnimationTime::updateStartTime() -{ - setGroup(mWeaponGroup); -} - static NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; @@ -147,8 +126,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v mShowCarriedLeft(true), mFirstPersonOffset(0.f, 0.f, 0.f), mAlpha(1.f), - mNpcType(Type_Normal), - mPitchFactor(0) + mNpcType(Type_Normal) { mNpc = mPtr.get()->mBase; @@ -538,14 +516,10 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) // updateSkeletonInstance, below, touches the hands. node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD); } - else if (mPitchFactor > 0) + else { // In third person mode we may still need pitch for ranged weapon targeting - float pitch = mPtr.getRefData().getPosition().rot[0] * mPitchFactor; - Ogre::Node *node = baseinst->getBone("Bip01 Spine2"); - node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); - node = baseinst->getBone("Bip01 Spine1"); - node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); + pitchSkeleton(mPtr.getRefData().getPosition().rot[0], baseinst); } mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. @@ -695,13 +669,14 @@ void NpcAnimation::showWeapons(bool showWeapon) { MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon != inv.end()) // special case for weapons + if(weapon != inv.end()) { Ogre::Vector3 glowColor = getEnchantmentColor(*weapon); std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon); addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); + // Crossbows start out with a bolt attached if (weapon->getTypeName() == typeid(ESM::Weapon).name() && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { @@ -743,113 +718,24 @@ void NpcAnimation::showCarriedLeft(bool show) removeIndividualPart(ESM::PRT_Shield); } +void NpcAnimation::configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) +{ + Ogre::Vector3 glowColor = getEnchantmentColor(ptr); + setRenderProperties(object, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, + !ptr.getClass().getEnchantment(ptr).empty(), &glowColor); + + std::for_each(object->mEntities.begin(), object->mEntities.end(), SetObjectGroup(slot)); + std::for_each(object->mParticles.begin(), object->mParticles.end(), SetObjectGroup(slot)); +} + void NpcAnimation::attachArrow() { - MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weaponSlot != inv.end() && weaponSlot->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) - showWeapons(true); - else - { - NifOgre::ObjectScenePtr weapon = mObjectParts[ESM::PRT_Weapon]; - - MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo == inv.end()) - return; - std::string model = ammo->getClass().getModel(*ammo); - - mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", mInsert, model); - Ogre::Vector3 glowColor = getEnchantmentColor(*ammo); - setRenderProperties(mAmmunition, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, - !ammo->getClass().getEnchantment(*ammo).empty(), &glowColor); - - std::for_each(mAmmunition->mEntities.begin(), mAmmunition->mEntities.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); - std::for_each(mAmmunition->mParticles.begin(), mAmmunition->mParticles.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); - } + WeaponAnimation::attachArrow(mPtr); } void NpcAnimation::releaseArrow() { - MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weapon == inv.end()) - return; - - // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. - Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(mPtr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(mPtr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); - - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - - // Reduce fatigue - // somewhat of a guess, but using the weapon weight makes sense - const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat(); - const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat(); - const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat(); - MWMechanics::CreatureStats& attackerStats = mPtr.getClass().getCreatureStats(mPtr); - MWMechanics::DynamicStat fatigue = attackerStats.getFatigue(); - const float normalizedEncumbrance = mPtr.getClass().getEncumbrance(mPtr) / mPtr.getClass().getCapacity(mPtr); - float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; - if (!weapon->isEmpty()) - fatigueLoss += weapon->getClass().getWeight(*weapon) * attackerStats.getAttackStrength() * fWeaponFatigueMult; - fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); - attackerStats.setFatigue(fatigue); - - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) - { - // Thrown weapons get detached now - NifOgre::ObjectScenePtr objects = mObjectParts[ESM::PRT_Weapon]; - - Ogre::Vector3 launchPos(0,0,0); - if (objects->mSkelBase) - { - launchPos = objects->mSkelBase->getParentNode()->_getDerivedPosition(); - } - else if (objects->mEntities.size()) - { - objects->mEntities[0]->getParentNode()->needUpdate(true); - launchPos = objects->mEntities[0]->getParentNode()->_getDerivedPosition(); - } - - float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat(); - float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); - float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * - mPtr.getClass().getCreatureStats(mPtr).getAttackStrength(); - - MWBase::Environment::get().getWorld()->launchProjectile(mPtr, *weapon, launchPos, orient, *weapon, speed); - - showWeapons(false); - - inv.remove(*weapon, 1, mPtr); - } - else - { - // With bows and crossbows only the used arrow/bolt gets detached - MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo == inv.end()) - return; - - Ogre::Vector3 launchPos(0,0,0); - if (mAmmunition->mSkelBase) - { - launchPos = mAmmunition->mSkelBase->getParentNode()->_getDerivedPosition(); - } - else if (mAmmunition->mEntities.size()) - { - mAmmunition->mEntities[0]->getParentNode()->needUpdate(true); - launchPos = mAmmunition->mEntities[0]->getParentNode()->_getDerivedPosition(); - } - - float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat(); - float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); - float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * mPtr.getClass().getCreatureStats(mPtr).getAttackStrength(); - - MWBase::Environment::get().getWorld()->launchProjectile(mPtr, *ammo, launchPos, orient, *weapon, speed); - - inv.remove(*ammo, 1, mPtr); - mAmmunition.setNull(); - } + WeaponAnimation::releaseArrow(mPtr); } void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 725fde01d..8ec46facd 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -5,6 +5,8 @@ #include "../mwworld/inventorystore.hpp" +#include "weaponanimation.hpp" + namespace ESM { struct NPC; @@ -25,24 +27,7 @@ public: { } }; -class WeaponAnimationTime : public Ogre::ControllerValue -{ -private: - Animation* mAnimation; - std::string mWeaponGroup; - float mStartTime; -public: - WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {} - void setGroup(const std::string& group); - void updateStartTime(); - - virtual Ogre::Real getValue() const; - virtual void setValue(Ogre::Real value) - { } -}; - - -class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener +class NpcAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: virtual void equipmentChanged() { updateParts(); } @@ -91,7 +76,6 @@ private: Ogre::SharedPtr mWeaponAnimationTime; float mAlpha; - float mPitchFactor; void updateNpcBase(); @@ -138,7 +122,10 @@ public: virtual void attachArrow(); virtual void releaseArrow(); - NifOgre::ObjectScenePtr mAmmunition; + // WeaponAnimation + virtual NifOgre::ObjectScenePtr getWeapon() { return mObjectParts[ESM::PRT_Weapon]; } + virtual void showWeapon(bool show) { showWeapons(show); } + virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot); void setViewMode(ViewMode viewMode); diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp new file mode 100644 index 000000000..5f953bd83 --- /dev/null +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -0,0 +1,159 @@ +#include "weaponanimation.hpp" + +#include +#include +#include +#include + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "../mwmechanics/creaturestats.hpp" + +#include "animation.hpp" + +namespace MWRender +{ + +float WeaponAnimationTime::getValue() const +{ + if (mWeaponGroup.empty()) + return 0; + float current = mAnimation->getCurrentTime(mWeaponGroup); + if (current == -1) + return 0; + return current - mStartTime; +} + +void WeaponAnimationTime::setGroup(const std::string &group) +{ + mWeaponGroup = group; + mStartTime = mAnimation->getStartTime(mWeaponGroup); +} + +void WeaponAnimationTime::updateStartTime() +{ + setGroup(mWeaponGroup); +} + +void WeaponAnimation::attachArrow(MWWorld::Ptr actor) +{ + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponSlot != inv.end() && weaponSlot->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + showWeapon(true); + else + { + NifOgre::ObjectScenePtr weapon = getWeapon(); + + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + std::string model = ammo->getClass().getModel(*ammo); + + mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", weapon->mSkelBase->getParentSceneNode(), model); + configureAddedObject(mAmmunition, *ammo, MWWorld::InventoryStore::Slot_Ammunition); + } +} + +void WeaponAnimation::releaseArrow(MWWorld::Ptr actor) +{ + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon == inv.end()) + return; + + // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. + Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + + // Reduce fatigue + // somewhat of a guess, but using the weapon weight makes sense + const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat(); + const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat(); + const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat(); + MWMechanics::CreatureStats& attackerStats = actor.getClass().getCreatureStats(actor); + MWMechanics::DynamicStat fatigue = attackerStats.getFatigue(); + const float normalizedEncumbrance = actor.getClass().getEncumbrance(actor) / actor.getClass().getCapacity(actor); + float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; + if (!weapon->isEmpty()) + fatigueLoss += weapon->getClass().getWeight(*weapon) * attackerStats.getAttackStrength() * fWeaponFatigueMult; + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); + attackerStats.setFatigue(fatigue); + + if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + { + // Thrown weapons get detached now + NifOgre::ObjectScenePtr objects = getWeapon(); + + Ogre::Vector3 launchPos(0,0,0); + if (objects->mSkelBase) + { + launchPos = objects->mSkelBase->getParentNode()->_getDerivedPosition(); + } + else if (objects->mEntities.size()) + { + objects->mEntities[0]->getParentNode()->needUpdate(true); + launchPos = objects->mEntities[0]->getParentNode()->_getDerivedPosition(); + } + + float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat(); + float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); + float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * + actor.getClass().getCreatureStats(actor).getAttackStrength(); + + MWBase::Environment::get().getWorld()->launchProjectile(actor, *weapon, launchPos, orient, *weapon, speed); + + showWeapon(false); + + inv.remove(*weapon, 1, actor); + } + else + { + // With bows and crossbows only the used arrow/bolt gets detached + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + + Ogre::Vector3 launchPos(0,0,0); + if (mAmmunition->mSkelBase) + { + launchPos = mAmmunition->mSkelBase->getParentNode()->_getDerivedPosition(); + } + else if (mAmmunition->mEntities.size()) + { + mAmmunition->mEntities[0]->getParentNode()->needUpdate(true); + launchPos = mAmmunition->mEntities[0]->getParentNode()->_getDerivedPosition(); + } + + float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat(); + float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); + float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * actor.getClass().getCreatureStats(actor).getAttackStrength(); + + MWBase::Environment::get().getWorld()->launchProjectile(actor, *ammo, launchPos, orient, *weapon, speed); + + inv.remove(*ammo, 1, actor); + mAmmunition.setNull(); + } +} + +void WeaponAnimation::pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel) +{ + if (mPitchFactor == 0) + return; + + float pitch = xrot * mPitchFactor; + Ogre::Node *node = skel->getBone("Bip01 Spine2"); + node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); + node = skel->getBone("Bip01 Spine1"); + node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); +} + +} diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp new file mode 100644 index 000000000..c09aa65d9 --- /dev/null +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -0,0 +1,56 @@ +#ifndef OPENMW_MWRENDER_WEAPONANIMATION_H +#define OPENMW_MWRENDER_WEAPONANIMATION_H + +#include + +#include + +#include "../mwworld/ptr.hpp" + +namespace MWRender +{ + + class Animation; + + class WeaponAnimationTime : public Ogre::ControllerValue + { + private: + Animation* mAnimation; + std::string mWeaponGroup; + float mStartTime; + public: + WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {} + void setGroup(const std::string& group); + void updateStartTime(); + + virtual Ogre::Real getValue() const; + virtual void setValue(Ogre::Real value) + { } + }; + + /// Handles attach & release of projectiles for ranged weapons + class WeaponAnimation + { + public: + WeaponAnimation() : mPitchFactor(0) {} + + virtual void attachArrow(MWWorld::Ptr actor); + virtual void releaseArrow(MWWorld::Ptr actor); + + protected: + NifOgre::ObjectScenePtr mAmmunition; + + virtual NifOgre::ObjectScenePtr getWeapon() = 0; + virtual void showWeapon(bool show) = 0; + virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) = 0; + + /// A relative factor (0-1) that decides if and how much the skeleton should be pitched + /// to indicate the facing orientation of the character, for ranged weapon aiming. + float mPitchFactor; + + void pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel); + }; + +} + +#endif