1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-04-01 20:06:41 +00:00

Use an inventory store listener for animation parts and VFX update instead of updating them directly. Slightly more flexible, reduces InventoryStore dependencies and solves a crash during character creation due to the preview doll's animation not being registered in World.

This commit is contained in:
scrawl 2013-11-15 02:08:36 +01:00
parent 427de69b79
commit 00af6b5617
8 changed files with 131 additions and 78 deletions

View file

@ -71,7 +71,7 @@ namespace MWRender
mNode = renderRoot->createChildSceneNode(); mNode = renderRoot->createChildSceneNode();
mAnimation = new NpcAnimation(mCharacter, mNode, mAnimation = new NpcAnimation(mCharacter, mNode,
0, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); 0, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal));
mAnimation->updateParts(); mAnimation->updateParts();
Ogre::Vector3 scale = mNode->getScale(); Ogre::Vector3 scale = mNode->getScale();
@ -102,7 +102,6 @@ namespace MWRender
{ {
if (mSceneMgr) if (mSceneMgr)
{ {
//Ogre::TextureManager::getSingleton().remove(mName);
mSceneMgr->destroyAllCameras(); mSceneMgr->destroyAllCameras();
delete mAnimation; delete mAnimation;
Ogre::Root::getSingleton().destroySceneManager(mSceneMgr); Ogre::Root::getSingleton().destroySceneManager(mSceneMgr);
@ -114,7 +113,7 @@ namespace MWRender
assert(mAnimation); assert(mAnimation);
delete mAnimation; delete mAnimation;
mAnimation = new NpcAnimation(mCharacter, mNode, mAnimation = new NpcAnimation(mCharacter, mNode,
0, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); 0, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal));
mAnimation->updateParts(); mAnimation->updateParts();
float scale=1.f; float scale=1.f;

View file

@ -14,6 +14,7 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "renderconst.hpp" #include "renderconst.hpp"
#include "camera.hpp" #include "camera.hpp"
@ -58,17 +59,19 @@ const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap();
NpcAnimation::~NpcAnimation() NpcAnimation::~NpcAnimation()
{ {
if (!mListenerDisabled)
mPtr.getClass().getInventoryStore(mPtr).setListener(NULL);
Ogre::SceneManager *sceneMgr = mInsert->getCreator(); Ogre::SceneManager *sceneMgr = mInsert->getCreator();
for(size_t i = 0;i < ESM::PRT_Count;i++) for(size_t i = 0;i < ESM::PRT_Count;i++)
destroyObjectList(sceneMgr, mObjectParts[i]); destroyObjectList(sceneMgr, mObjectParts[i]);
} }
NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, ViewMode viewMode) NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, bool disableListener, ViewMode viewMode)
: Animation(ptr, node), : Animation(ptr, node),
mStateID(-1),
mVisibilityFlags(visibilityFlags), mVisibilityFlags(visibilityFlags),
mListenerDisabled(disableListener),
mViewMode(viewMode), mViewMode(viewMode),
mShowWeapons(false), mShowWeapons(false),
mFirstPersonOffset(0.f, 0.f, 0.f) mFirstPersonOffset(0.f, 0.f, 0.f)
@ -80,6 +83,11 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v
mPartslots[i] = -1; //each slot is empty mPartslots[i] = -1; //each slot is empty
mPartPriorities[i] = 0; mPartPriorities[i] = 0;
} }
if (!disableListener)
mPtr.getClass().getInventoryStore(mPtr).setListener(this);
updateNpcBase();
} }
void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
@ -162,13 +170,6 @@ void NpcAnimation::updateNpcBase()
void NpcAnimation::updateParts() void NpcAnimation::updateParts()
{ {
if (!mSkelBase)
{
// First update?
updateNpcBase();
return;
}
const MWWorld::Class &cls = MWWorld::Class::get(mPtr); const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
@ -417,9 +418,6 @@ NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, in
Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) Ogre::Vector3 NpcAnimation::runAnimation(float timepassed)
{ {
if (!mSkelBase)
updateNpcBase();
Ogre::Vector3 ret = Animation::runAnimation(timepassed); Ogre::Vector3 ret = Animation::runAnimation(timepassed);
Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton();
@ -586,4 +584,32 @@ void NpcAnimation::showWeapons(bool showWeapon)
} }
} }
void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound)
{
// 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)
{
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
if(!magicEffect->mHitSound.empty())
sndMgr->playSound3D(mPtr, magicEffect->mHitSound, 1.0f, 1.0f);
else
sndMgr->playSound3D(mPtr, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
}
if (!magicEffect->mHit.empty())
{
const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
// 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, "");
}
}
} }

View file

@ -3,23 +3,22 @@
#include "animation.hpp" #include "animation.hpp"
#include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp"
namespace ESM namespace ESM
{ {
struct NPC; struct NPC;
} }
namespace MWWorld
{
class InventoryStore;
}
namespace MWRender namespace MWRender
{ {
class NpcAnimation : public Animation class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener
{ {
public:
virtual void equipmentChanged() { updateParts(); }
virtual void permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound);
public: public:
typedef std::map<ESM::PartReferenceType,std::string> PartBoneMap; typedef std::map<ESM::PartReferenceType,std::string> PartBoneMap;
@ -32,7 +31,7 @@ public:
private: private:
static const PartBoneMap sPartList; static const PartBoneMap sPartList;
int mStateID; bool mListenerDisabled;
// Bounded Parts // Bounded Parts
NifOgre::ObjectList mObjectParts[ESM::PRT_Count]; NifOgre::ObjectList mObjectParts[ESM::PRT_Count];
@ -62,7 +61,17 @@ private:
void addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts); void addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts);
public: public:
NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, /**
* @param ptr
* @param node
* @param visibilityFlags
* @param disableListener Don't listen for equipment changes and magic effects. InventoryStore only supports
* one listener at a time, so you shouldn't do this if creating several NpcAnimations
* for the same Ptr, eg preview dolls for the player.
* Those need to be manually rendered anyway.
* @param viewMode
*/
NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, bool disableListener = false,
ViewMode viewMode=VM_Normal); ViewMode viewMode=VM_Normal);
virtual ~NpcAnimation(); virtual ~NpcAnimation();

View file

@ -135,7 +135,10 @@ namespace MWScript
MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr);
std::string itemName = ""; std::string itemName;
for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter)
if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item))
itemName = iter->getClass().getName(*iter);
int numRemoved = store.remove(item, count, ptr); int numRemoved = store.remove(item, count, ptr);

View file

@ -63,7 +63,7 @@ namespace
} }
} }
MWWorld::ContainerStore::ContainerStore() : mStateId (0), mCachedWeight (0), mWeightUpToDate (false) {} MWWorld::ContainerStore::ContainerStore() : mCachedWeight (0), mWeightUpToDate (false) {}
MWWorld::ContainerStore::~ContainerStore() {} MWWorld::ContainerStore::~ContainerStore() {}

View file

@ -49,7 +49,6 @@ namespace MWWorld
MWWorld::CellRefList<ESM::Probe> probes; MWWorld::CellRefList<ESM::Probe> probes;
MWWorld::CellRefList<ESM::Repair> repairs; MWWorld::CellRefList<ESM::Repair> repairs;
MWWorld::CellRefList<ESM::Weapon> weapons; MWWorld::CellRefList<ESM::Weapon> weapons;
int mStateId;
mutable float mCachedWeight; mutable float mCachedWeight;
mutable bool mWeightUpToDate; mutable bool mWeightUpToDate;
ContainerStoreIterator addImp (const Ptr& ptr); ContainerStoreIterator addImp (const Ptr& ptr);

View file

@ -9,9 +9,6 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwrender/animation.hpp"
#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/npcstats.hpp"
@ -46,6 +43,7 @@ MWWorld::InventoryStore::InventoryStore()
: mSelectedEnchantItem(end()) : mSelectedEnchantItem(end())
, mUpdatesEnabled (true) , mUpdatesEnabled (true)
, mFirstAutoEquip(true) , mFirstAutoEquip(true)
, mListener(NULL)
{ {
initSlots (mSlots); initSlots (mSlots);
} }
@ -53,6 +51,8 @@ MWWorld::InventoryStore::InventoryStore()
MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) MWWorld::InventoryStore::InventoryStore (const InventoryStore& store)
: ContainerStore (store) : ContainerStore (store)
, mSelectedEnchantItem(end()) , mSelectedEnchantItem(end())
, mListener(NULL)
, mUpdatesEnabled(true)
{ {
mMagicEffects = store.mMagicEffects; mMagicEffects = store.mMagicEffects;
mFirstAutoEquip = store.mFirstAutoEquip; mFirstAutoEquip = store.mFirstAutoEquip;
@ -122,9 +122,8 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite
flagAsModified(); flagAsModified();
updateActorModel(actor); fireEquipmentChangedEvent();
updateMagicEffects();
updateMagicEffects(actor);
} }
void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor)
@ -255,11 +254,10 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc)
if (changed) if (changed)
{ {
mSlots.swap (slots_); mSlots.swap (slots_);
updateActorModel(npc); fireEquipmentChangedEvent();
updateMagicEffects(npc); updateMagicEffects();
flagAsModified(); flagAsModified();
} }
mFirstAutoEquip = false;
} }
const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const
@ -267,12 +265,16 @@ const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() cons
return mMagicEffects; return mMagicEffects;
} }
void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) void MWWorld::InventoryStore::updateMagicEffects()
{ {
// To avoid excessive updates during auto-equip // To avoid excessive updates during auto-equip
if (!mUpdatesEnabled) if (!mUpdatesEnabled)
return; return;
// Delay update until the listener is set up
if (!mListener)
return;
mMagicEffects = MWMechanics::MagicEffects(); mMagicEffects = MWMechanics::MagicEffects();
for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
@ -296,6 +298,16 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
for (unsigned int i=0; i<random.size();++i) for (unsigned int i=0; i<random.size();++i)
random[i] = static_cast<float> (std::rand()) / RAND_MAX; random[i] = static_cast<float> (std::rand()) / RAND_MAX;
bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) != mPermanentMagicEffectMagnitudes.end());
if (!existed)
{
// Note that using the RefID as a key here is not entirely correct.
// Consider equipping the same item twice (e.g. a ring)
// However, permanent enchantments with a random magnitude are kind of an exploit anyway,
// so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case.
mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = random;
}
int i=0; int i=0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (enchantment.mEffects.mList.begin()); for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (enchantment.mEffects.mList.begin());
effectIt!=enchantment.mEffects.mList.end(); ++effectIt) effectIt!=enchantment.mEffects.mList.end(); ++effectIt)
@ -304,42 +316,13 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find ( MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effectIt->mEffectID); effectIt->mEffectID);
if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) == mPermanentMagicEffectMagnitudes.end()) if (!existed)
{ {
// Note that using the RefID as a key here is not entirely correct.
// Consider equipping the same item twice (e.g. a ring)
// However, permanent enchantments with a random magnitude are kind of an exploit anyway,
// so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case.
mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = random;
// During first auto equip, we don't play any sounds. // During first auto equip, we don't play any sounds.
// Basically we don't want sounds when the actor is first loaded, // Basically we don't want sounds when the actor is first loaded,
// the items should appear as if they'd always been equipped. // the items should appear as if they'd always been equipped.
if (!mFirstAutoEquip) mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip,
{ !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin());
// Only the sound of the first effect plays
if (effectIt == enchantment.mEffects.mList.begin())
{
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
if(!magicEffect->mHitSound.empty())
sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f);
else
sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
}
}
if (!magicEffect->mHit.empty())
{
const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
// Similar as above, we don't want particles during first autoequip either, unless they're continuous.
if (!mFirstAutoEquip || loop)
MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
}
} }
mMagicEffects.add (*effectIt, random[i]); mMagicEffects.add (*effectIt, random[i]);
@ -367,6 +350,8 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
else else
++it; ++it;
} }
mFirstAutoEquip = false;
} }
void MWWorld::InventoryStore::flagAsModified() void MWWorld::InventoryStore::flagAsModified()
@ -380,7 +365,7 @@ bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2)
if (!canStack) if (!canStack)
return false; return false;
// don't stack if 'stack' (the item being checked against) is currently equipped. // don't stack if either item is currently equipped
for (TSlots::const_iterator iter (mSlots.begin()); for (TSlots::const_iterator iter (mSlots.begin());
iter!=mSlots.end(); ++iter) iter!=mSlots.end(); ++iter)
{ {
@ -481,8 +466,8 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c
} }
} }
updateActorModel(actor); fireEquipmentChangedEvent();
updateMagicEffects(actor); updateMagicEffects();
return retval; return retval;
} }
@ -502,8 +487,16 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWor
throw std::runtime_error ("attempt to unequip an item that is not currently equipped"); throw std::runtime_error ("attempt to unequip an item that is not currently equipped");
} }
void MWWorld::InventoryStore::updateActorModel(const MWWorld::Ptr& actor) void MWWorld::InventoryStore::setListener(InventoryStoreListener *listener)
{ {
if (mUpdatesEnabled) mListener = listener;
MWBase::Environment::get().getWorld()->updateAnimParts(actor); updateMagicEffects();
}
void MWWorld::InventoryStore::fireEquipmentChangedEvent()
{
if (!mUpdatesEnabled)
return;
if (mListener)
mListener->equipmentChanged();
} }

View file

@ -12,6 +12,25 @@ namespace MWMechanics
namespace MWWorld namespace MWWorld
{ {
class InventoryStoreListener
{
public:
/**
* Fired when items are equipped or unequipped
*/
virtual void equipmentChanged () {}
/**
* @param effect
* @param isNew Is this effect new (e.g. the item for it was just now manually equipped)
* or was it loaded from a savegame / initial game state? \n
* 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) {}
};
///< \brief Variant of the ContainerStore for NPCs ///< \brief Variant of the ContainerStore for NPCs
class InventoryStore : public ContainerStore class InventoryStore : public ContainerStore
{ {
@ -45,6 +64,8 @@ namespace MWWorld
MWMechanics::MagicEffects mMagicEffects; MWMechanics::MagicEffects mMagicEffects;
InventoryStoreListener* mListener;
// Enables updates of magic effects and actor model whenever items are equipped or unequipped. // Enables updates of magic effects and actor model whenever items are equipped or unequipped.
// This is disabled during autoequip to avoid excessive updates // This is disabled during autoequip to avoid excessive updates
bool mUpdatesEnabled; bool mUpdatesEnabled;
@ -67,9 +88,9 @@ namespace MWWorld
void initSlots (TSlots& slots_); void initSlots (TSlots& slots_);
void updateActorModel (const Ptr& actor); void updateMagicEffects();
void updateMagicEffects(const Ptr& actor); void fireEquipmentChangedEvent();
public: public:
@ -138,6 +159,9 @@ namespace MWWorld
/// @return an iterator to the item that was previously in the slot /// @return an iterator to the item that was previously in the slot
/// (it can be re-stacked so its count may be different than when it /// (it can be re-stacked so its count may be different than when it
/// was equipped). /// was equipped).
void setListener (InventoryStoreListener* listener);
///< Set a listener for various events, see \a InventoryStoreListener
}; };
} }