From eefe1ed1a8aee482c0b7aa6d6e901404eb788391 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 21 Feb 2017 15:48:45 +0100 Subject: [PATCH 01/12] Fix being affected by Silence in god mode --- apps/openmw/mwmechanics/spellcasting.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index ffb31d70d..d2532db12 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -95,8 +95,6 @@ namespace MWMechanics int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2f * actorWillpower + 0.1f * actorLuck) * stats.getFatigueTerm(); - if (MWBase::Environment::get().getWorld()->getGodModeState() && actor == getPlayer()) - castChance = 100; if (!cap) return std::max(0.f, castChance); @@ -816,12 +814,15 @@ namespace MWMechanics bool fail = false; // Check success - float successChance = getSpellSuccessChance(spell, mCaster); - if (Misc::Rng::roll0to99() >= successChance) + if (!(mCaster == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState())) { - if (mCaster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); - fail = true; + float successChance = getSpellSuccessChance(spell, mCaster); + if (Misc::Rng::roll0to99() >= successChance) + { + if (mCaster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); + fail = true; + } } if (fail) From a45335ffc3f283b7f30478f1dfddb66126da93a4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 21 Feb 2017 16:33:18 +0100 Subject: [PATCH 02/12] Do not reduce magicka in god mode --- apps/openmw/mwworld/worldimp.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 91e10dca1..3d9300264 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2659,8 +2659,9 @@ namespace MWWorld const ESM::Spell* spell = getStore().get().find(selectedSpell); // Check mana + bool godmode = (isPlayer && getGodModeState()); MWMechanics::DynamicStat magicka = stats.getMagicka(); - if (magicka.getCurrent() < spell->mData.mCost && !(isPlayer && getGodModeState())) + if (magicka.getCurrent() < spell->mData.mCost && !godmode) { message = "#{sMagicInsufficientSP}"; fail = true; @@ -2674,7 +2675,7 @@ namespace MWWorld } // Reduce mana - if (!fail) + if (!fail && !godmode) { magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); stats.setMagicka(magicka); From c45013c983e70a9d3cba1bbdea91b52c8b24c250 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 21 Feb 2017 18:15:30 +0100 Subject: [PATCH 03/12] Fix particle systems not being rendered in their first frame --- components/nifosg/nifloader.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 9624e7396..d0febe2fb 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1038,10 +1038,12 @@ namespace NifOsg if (!(animflags & Nif::NiNode::ParticleFlag_AutoPlay)) { partsys->setFrozen(true); - // HACK: particle system will not render in Frozen state if there was no update - osg::NodeVisitor nv; - partsys->update(0.0, nv); } + + // Due to odd code in the ParticleSystemUpdater, particle systems will not be updated in the first frame + // So do that update manually + osg::NodeVisitor nv; + partsys->update(0.0, nv); } // affectors must be attached *after* the emitter in the scene graph for correct update order From e38221edc96c6e70686ee910a0053377e4cfe81b Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 21 Feb 2017 18:25:25 +0100 Subject: [PATCH 04/12] Set character preview's simulationTime to 0 to avoid flickering lights --- apps/openmw/mwrender/characterpreview.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 0d848bb11..28b46da41 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -42,7 +42,16 @@ namespace MWRender mRendered = true; mLastRenderedFrame = nv->getTraversalNumber(); + + osg::ref_ptr previousFramestamp = const_cast(nv->getFrameStamp()); + osg::FrameStamp* fs = new osg::FrameStamp(*previousFramestamp); + fs->setSimulationTime(0.0); + + nv->setFrameStamp(fs); + traverse(node, nv); + + nv->setFrameStamp(previousFramestamp); } else { From 0fbd29ccb8dbfa8d63d178721132db2a031da949 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 21 Feb 2017 18:29:18 +0100 Subject: [PATCH 05/12] Use additive alpha blending on the character preview --- apps/openmw/mwrender/characterpreview.cpp | 41 +++++++++++++++++++++++ apps/openmw/mwrender/characterpreview.hpp | 1 + 2 files changed, 42 insertions(+) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 28b46da41..8c0ec3b80 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -74,6 +75,35 @@ namespace MWRender unsigned int mLastRenderedFrame; }; + + // Set up alpha blending to Additive mode to avoid issues caused by transparent objects writing onto the alpha value of the FBO + class SetUpBlendVisitor : public osg::NodeVisitor + { + public: + SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + virtual void apply(osg::Node& node) + { + if (osg::StateSet* stateset = node.getStateSet()) + { + if (stateset->getAttribute(osg::StateAttribute::BLENDFUNC) || stateset->getBinNumber() == osg::StateSet::TRANSPARENT_BIN) + { + osg::ref_ptr newStateSet = new osg::StateSet(*stateset, osg::CopyOp::SHALLOW_COPY); + osg::BlendFunc* blendFunc = static_cast(stateset->getAttribute(osg::StateAttribute::BLENDFUNC)); + osg::ref_ptr newBlendFunc = blendFunc ? new osg::BlendFunc(*blendFunc) : new osg::BlendFunc; + newBlendFunc->setDestinationAlpha(osg::BlendFunc::ONE); + newBlendFunc->setDestinationRGB(osg::BlendFunc::ONE); + newStateSet->setAttribute(newBlendFunc, osg::StateAttribute::ON); + node.setStateSet(newStateSet); + } + + } + traverse(node); + } + }; + CharacterPreview::CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, MWWorld::Ptr character, int sizeX, int sizeY, const osg::Vec3f& position, const osg::Vec3f& lookAt) : mParent(parent) @@ -170,8 +200,15 @@ namespace MWRender return mSizeY; } + void CharacterPreview::setBlendMode() + { + SetUpBlendVisitor visitor; + mNode->accept(visitor); + } + void CharacterPreview::onSetup() { + setBlendMode(); } osg::ref_ptr CharacterPreview::getTexture() @@ -280,6 +317,8 @@ namespace MWRender mAnimation->runAnimation(0.0f); + setBlendMode(); + redraw(); } @@ -319,6 +358,7 @@ namespace MWRender void InventoryPreview::onSetup() { + CharacterPreview::onSetup(); osg::Vec3f scale (1.f, 1.f, 1.f); mCharacter.getClass().adjustScale(mCharacter, scale, true); @@ -392,6 +432,7 @@ namespace MWRender void RaceSelectionPreview::onSetup () { + CharacterPreview::onSetup(); mAnimation->play("idle", 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); mAnimation->runAnimation(0.f); diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 2a4ef9877..e6480e0b3 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -47,6 +47,7 @@ namespace MWRender protected: virtual bool renderHeadOnly() { return false; } + void setBlendMode(); virtual void onSetup(); osg::ref_ptr mParent; From 3693f05ef5b18af9de2a49bbaf7f828a8852eec3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 21 Feb 2017 18:59:38 +0100 Subject: [PATCH 06/12] Transform world-space particle systems in update callback so that animations are accounted for --- components/resource/scenemanager.cpp | 66 +++++++++++++++++----------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 9cda7d755..fbb4f2889 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -31,12 +31,47 @@ namespace { - /// @todo Do this in updateCallback so that animations are accounted for. - class InitWorldSpaceParticlesVisitor : public osg::NodeVisitor + class InitWorldSpaceParticlesCallback : public osg::NodeCallback + { + public: + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgParticle::ParticleSystem* partsys = static_cast(node); + + // HACK: Ignore the InverseWorldMatrix transform the particle system is attached to + if (partsys->getNumParents() && partsys->getParent(0)->getNumParents()) + transformInitialParticles(partsys, partsys->getParent(0)->getParent(0)); + + node->removeUpdateCallback(this); + } + + void transformInitialParticles(osgParticle::ParticleSystem* partsys, osg::Node* node) + { + osg::NodePathList nodepaths = node->getParentalNodePaths(); + if (nodepaths.empty()) + return; + osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]); + worldMat.orthoNormalize(worldMat); // scale is already applied on the particle node + for (int i=0; inumParticles(); ++i) + { + partsys->getParticle(i)->transformPositionVelocity(worldMat); + } + + // transform initial bounds to worldspace + osg::BoundingSphere sphere(partsys->getInitialBound()); + SceneUtil::transformBoundingSphere(worldMat, sphere); + osg::BoundingBox box; + box.expandBy(sphere); + partsys->setInitialBound(box); + } + + }; + + class InitParticlesVisitor : public osg::NodeVisitor { public: /// @param mask The node mask to set on ParticleSystem nodes. - InitWorldSpaceParticlesVisitor(unsigned int mask) + InitParticlesVisitor(unsigned int mask) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mMask(mask) { @@ -56,33 +91,12 @@ namespace { if (isWorldSpaceParticleSystem(partsys)) { - // HACK: Ignore the InverseWorldMatrix transform the particle system is attached to - if (partsys->getNumParents() && partsys->getParent(0)->getNumParents()) - transformInitialParticles(partsys, partsys->getParent(0)->getParent(0)); + partsys->addUpdateCallback(new InitWorldSpaceParticlesCallback); } partsys->setNodeMask(mMask); } } - void transformInitialParticles(osgParticle::ParticleSystem* partsys, osg::Node* node) - { - osg::NodePathList nodepaths = node->getParentalNodePaths(); - if (nodepaths.empty()) - return; - osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]); - worldMat.orthoNormalize(worldMat); // scale is already applied on the particle node - for (int i=0; inumParticles(); ++i) - { - partsys->getParticle(i)->transformPositionVelocity(worldMat); - } - - // transform initial bounds to worldspace - osg::BoundingSphere sphere(partsys->getInitialBound()); - SceneUtil::transformBoundingSphere(worldMat, sphere); - osg::BoundingBox box; - box.expandBy(sphere); - partsys->setInitialBound(box); - } private: unsigned int mMask; }; @@ -489,7 +503,7 @@ namespace Resource // we can skip any scene graphs without update callbacks since we know that particle emitters will have an update callback set if (node->getNumChildrenRequiringUpdateTraversal() > 0) { - InitWorldSpaceParticlesVisitor visitor (mParticleSystemMask); + InitParticlesVisitor visitor (mParticleSystemMask); node->accept(visitor); } } From b40ca9b60a24f90e6b732f3904fe0544703dde6b Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 22 Feb 2017 01:49:54 +0100 Subject: [PATCH 07/12] Set the window rectangle on the Viewer's EventQueue --- apps/openmw/engine.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index d54eed3a6..61923af27 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -402,6 +402,8 @@ void OMW::Engine::createWindow(Settings::Manager& settings) camera->setViewport(0, 0, width, height); mViewer->realize(); + + mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, width, height); } void OMW::Engine::setWindowIcon() From 8f79fa3d7278bb6a64dbeefb4dc0c5d3cafe7dd7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 22 Feb 2017 02:18:18 +0100 Subject: [PATCH 08/12] Add resource statistics panel opened with F4 --- apps/openmw/engine.cpp | 13 +- apps/openmw/mwrender/renderingmanager.cpp | 14 + apps/openmw/mwrender/renderingmanager.hpp | 2 + components/CMakeLists.txt | 3 +- components/resource/bulletshapemanager.cpp | 6 + components/resource/bulletshapemanager.hpp | 2 + components/resource/imagemanager.cpp | 5 + components/resource/imagemanager.hpp | 2 + components/resource/keyframemanager.cpp | 5 + components/resource/keyframemanager.hpp | 2 + components/resource/multiobjectcache.cpp | 6 + components/resource/multiobjectcache.hpp | 4 +- components/resource/niffilemanager.cpp | 6 + components/resource/niffilemanager.hpp | 2 + components/resource/objectcache.cpp | 6 + components/resource/objectcache.hpp | 5 +- components/resource/resourcemanager.hpp | 7 + components/resource/resourcesystem.cpp | 6 + components/resource/resourcesystem.hpp | 7 + components/resource/scenemanager.cpp | 11 + components/resource/scenemanager.hpp | 2 + components/resource/stats.cpp | 316 ++++++++++++++++++ components/resource/stats.hpp | 59 ++++ components/sceneutil/unrefqueue.cpp | 5 + components/sceneutil/unrefqueue.hpp | 2 + components/sceneutil/workqueue.cpp | 24 ++ components/sceneutil/workqueue.hpp | 9 +- components/sdlutil/sdlinputwrapper.cpp | 10 +- components/terrain/terraingrid.cpp | 12 + components/terrain/terraingrid.hpp | 2 + components/terrain/world.hpp | 3 + .../reference/modding/settings/cells.rst | 2 + 32 files changed, 549 insertions(+), 11 deletions(-) create mode 100644 components/resource/stats.cpp create mode 100644 components/resource/stats.hpp diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 61923af27..371e86545 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -21,6 +21,7 @@ #include #include +#include #include @@ -169,7 +170,7 @@ void OMW::Engine::frame(float frametime) mEnvironment.getWindowManager()->update(); } - int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); + unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); osg::Stats* stats = mViewer->getViewerStats(); stats->setAttribute(frameNumber, "script_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeScriptTick)); stats->setAttribute(frameNumber, "script_time_taken", osg::Timer::instance()->delta_s(beforeScriptTick, afterScriptTick)); @@ -183,6 +184,14 @@ void OMW::Engine::frame(float frametime) stats->setAttribute(frameNumber, "physics_time_taken", osg::Timer::instance()->delta_s(beforePhysicsTick, afterPhysicsTick)); stats->setAttribute(frameNumber, "physics_time_end", osg::Timer::instance()->delta_s(mStartTick, afterPhysicsTick)); + if (stats->collectStats("resource")) + { + mResourceSystem->reportStats(frameNumber, stats); + + stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems()); + stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads()); + } + } catch (const std::exception& e) { @@ -635,6 +644,8 @@ void OMW::Engine::go() mViewer->addEventHandler(statshandler); + mViewer->addEventHandler(new Resource::StatsHandler); + Settings::Manager settings; std::string settingspath; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 33bb84345..09e1bb026 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -475,6 +475,8 @@ namespace MWRender void RenderingManager::update(float dt, bool paused) { + reportStats(); + mUnrefQueue->flush(mWorkQueue.get()); if (!paused) @@ -901,6 +903,18 @@ namespace MWRender mStateUpdater->setFogColor(color); } + void RenderingManager::reportStats() + { + osg::Stats* stats = mViewer->getViewerStats(); + unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); + if (stats->collectStats("resource")) + { + stats->setAttribute(frameNumber, "UnrefQueue", mUnrefQueue->getNumItems()); + + mTerrain->reportStats(frameNumber, stats); + } + } + void RenderingManager::processChangedSettings(const Settings::CategorySettingVector &changed) { for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index eca56f8b5..fc72eb279 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -196,6 +196,8 @@ namespace MWRender void updateAmbient(); void setFogColor(const osg::Vec4f& color); + void reportStats(); + osg::ref_ptr mViewer; osg::ref_ptr mRootNode; osg::ref_ptr mSceneRoot; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 48a414b38..f08086189 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -41,7 +41,7 @@ add_component_dir (vfs ) add_component_dir (resource - scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem resourcemanager + scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem resourcemanager stats ) add_component_dir (shader @@ -202,6 +202,7 @@ target_link_libraries(components ${OSGUTIL_LIBRARIES} ${OSGDB_LIBRARIES} ${OSGVIEWER_LIBRARIES} + ${OSGTEXT_LIBRARIES} ${OSGGA_LIBRARIES} ${OSGFX_LIBRARIES} ${OSGANIMATION_LIBRARIES} diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 0a5ed783e..18c37d97b 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -185,4 +185,10 @@ void BulletShapeManager::updateCache(double referenceTime) mInstanceCache->removeUnreferencedObjectsInCache(); } +void BulletShapeManager::reportStats(unsigned int frameNumber, osg::Stats *stats) +{ + stats->setAttribute(frameNumber, "Shape", mCache->getCacheSize()); + stats->setAttribute(frameNumber, "Shape Instance", mInstanceCache->getCacheSize()); +} + } diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index 14b26962b..e6dba001c 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -42,6 +42,8 @@ namespace Resource /// @see ResourceManager::updateCache virtual void updateCache(double referenceTime); + void reportStats(unsigned int frameNumber, osg::Stats *stats); + private: osg::ref_ptr createInstance(const std::string& name); diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 96c5c66f5..2e1422cf6 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -141,4 +141,9 @@ namespace Resource return mWarningImage; } + void ImageManager::reportStats(unsigned int frameNumber, osg::Stats *stats) + { + stats->setAttribute(frameNumber, "Image", mCache->getCacheSize()); + } + } diff --git a/components/resource/imagemanager.hpp b/components/resource/imagemanager.hpp index 2ef601e6b..2c7a61828 100644 --- a/components/resource/imagemanager.hpp +++ b/components/resource/imagemanager.hpp @@ -32,6 +32,8 @@ namespace Resource osg::Image* getWarningImage(); + void reportStats(unsigned int frameNumber, osg::Stats* stats); + private: osg::ref_ptr mWarningImage; osg::ref_ptr mOptions; diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 50dfbd9a3..699fdfdd6 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -34,6 +34,11 @@ namespace Resource } } + void KeyframeManager::reportStats(unsigned int frameNumber, osg::Stats *stats) + { + stats->setAttribute(frameNumber, "Keyframe", mCache->getCacheSize()); + } + } diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index ab33d51e3..a5c1a7818 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -22,6 +22,8 @@ namespace Resource /// Retrieve a read-only keyframe resource by name (case-insensitive). /// @note Throws an exception if the resource is not found. osg::ref_ptr get(const std::string& name); + + void reportStats(unsigned int frameNumber, osg::Stats* stats); }; } diff --git a/components/resource/multiobjectcache.cpp b/components/resource/multiobjectcache.cpp index 352715f19..fcda455cc 100644 --- a/components/resource/multiobjectcache.cpp +++ b/components/resource/multiobjectcache.cpp @@ -76,4 +76,10 @@ namespace Resource } } + unsigned int MultiObjectCache::getCacheSize() const + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + return _objectCache.size(); + } + } diff --git a/components/resource/multiobjectcache.hpp b/components/resource/multiobjectcache.hpp index b677100f0..a314a9e4b 100644 --- a/components/resource/multiobjectcache.hpp +++ b/components/resource/multiobjectcache.hpp @@ -33,12 +33,14 @@ namespace Resource /** call releaseGLObjects on all objects attached to the object cache.*/ void releaseGLObjects(osg::State* state); + unsigned int getCacheSize() const; + protected: typedef std::multimap > ObjectCacheMap; ObjectCacheMap _objectCache; - OpenThreads::Mutex _objectCacheMutex; + mutable OpenThreads::Mutex _objectCacheMutex; }; diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index 82768cc2c..5076e9d84 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -1,6 +1,7 @@ #include "niffilemanager.hpp" #include +#include #include @@ -55,4 +56,9 @@ namespace Resource } } + void NifFileManager::reportStats(unsigned int frameNumber, osg::Stats *stats) + { + stats->setAttribute(frameNumber, "Nif", mCache->getCacheSize()); + } + } diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp index 4b43ff24b..378daabf7 100644 --- a/components/resource/niffilemanager.hpp +++ b/components/resource/niffilemanager.hpp @@ -22,6 +22,8 @@ namespace Resource /// @note For performance reasons the NifFileManager does not handle case folding, needs /// to be done in advance by other managers accessing the NifFileManager. Nif::NIFFilePtr get(const std::string& name); + + void reportStats(unsigned int frameNumber, osg::Stats *stats); }; } diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp index d80561c24..de0fa7a40 100644 --- a/components/resource/objectcache.cpp +++ b/components/resource/objectcache.cpp @@ -150,4 +150,10 @@ void ObjectCache::accept(osg::NodeVisitor &nv) } } +unsigned int ObjectCache::getCacheSize() const +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + return _objectCache.size(); +} + } diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 3d65df546..e1caa4123 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -73,6 +73,9 @@ class ObjectCache : public osg::Referenced /** call node->accept(nv); for all nodes in the objectCache. */ void accept(osg::NodeVisitor& nv); + /** Get the number of objects in the cache. */ + unsigned int getCacheSize() const; + protected: virtual ~ObjectCache(); @@ -81,7 +84,7 @@ class ObjectCache : public osg::Referenced typedef std::map ObjectCacheMap; ObjectCacheMap _objectCache; - OpenThreads::Mutex _objectCacheMutex; + mutable OpenThreads::Mutex _objectCacheMutex; }; diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index b69256834..f07ac6f77 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -8,6 +8,11 @@ namespace VFS class Manager; } +namespace osg +{ + class Stats; +} + namespace Resource { class ObjectCache; @@ -28,6 +33,8 @@ namespace Resource const VFS::Manager* getVFS() const; + virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {} + protected: const VFS::Manager* mVFS; osg::ref_ptr mCache; diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 5772f2b85..091614a10 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -85,4 +85,10 @@ namespace Resource return mVFS; } + void ResourceSystem::reportStats(unsigned int frameNumber, osg::Stats *stats) + { + for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + (*it)->reportStats(frameNumber, stats); + } + } diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index 9b933ffc4..71541ade5 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -9,6 +9,11 @@ namespace VFS class Manager; } +namespace osg +{ + class Stats; +} + namespace Resource { @@ -49,6 +54,8 @@ namespace Resource /// @note May be called from any thread. const VFS::Manager* getVFS() const; + void reportStats(unsigned int frameNumber, osg::Stats* stats); + private: std::auto_ptr mSceneManager; std::auto_ptr mImageManager; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index fbb4f2889..070882bb8 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -585,4 +585,15 @@ namespace Resource mInstanceCache->removeUnreferencedObjectsInCache(); } + void SceneManager::reportStats(unsigned int frameNumber, osg::Stats *stats) + { + { + OpenThreads::ScopedLock lock(*mIncrementalCompileOperation->getToCompiledMutex()); + stats->setAttribute(frameNumber, "Compiling", mIncrementalCompileOperation->getToCompile().size()); + } + + stats->setAttribute(frameNumber, "Node", mCache->getCacheSize()); + stats->setAttribute(frameNumber, "Node Instance", mInstanceCache->getCacheSize()); + } + } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 97e5b43be..42da9b848 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -139,6 +139,8 @@ namespace Resource /// @see ResourceManager::updateCache virtual void updateCache(double referenceTime); + virtual void reportStats(unsigned int frameNumber, osg::Stats* stats); + private: osg::ref_ptr createInstance(const std::string& name); diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp new file mode 100644 index 000000000..5b0f8b935 --- /dev/null +++ b/components/resource/stats.cpp @@ -0,0 +1,316 @@ +#include "stats.hpp" + +#include +#include + +#include + +#include + +#include +#include + +namespace Resource +{ + +StatsHandler::StatsHandler(): + _key(osgGA::GUIEventAdapter::KEY_F4), + _initialized(false), + _statsType(false), + _statsWidth(1280.0f), + _statsHeight(1024.0f), + _font("fonts/arial.ttf"), + _characterSize(20.0f) +{ + _camera = new osg::Camera; + _camera->getOrCreateStateSet()->setGlobalDefaults(); + _camera->setRenderer(new osgViewer::Renderer(_camera.get())); + _camera->setProjectionResizePolicy(osg::Camera::FIXED); + + _resourceStatsChildNum = 0; +} + +bool StatsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) +{ + if (ea.getHandled()) return false; + + switch(ea.getEventType()) + { + case(osgGA::GUIEventAdapter::KEYDOWN): + { + if (ea.getKey()== _key) + { + osgViewer::View* myview = dynamic_cast(&aa); + if (!myview) return false; + + osgViewer::ViewerBase* viewer = myview->getViewerBase(); + + toggle(viewer); + + aa.requestRedraw(); + return true; + } + break; + } + case osgGA::GUIEventAdapter::RESIZE: + { + setWindowSize(ea.getWindowWidth(), ea.getWindowHeight()); + break; + } + default: + break; + } + return false; +} + +void StatsHandler::setWindowSize(int width, int height) +{ + if (width <= 0 || height <= 0) + return; + + _camera->setViewport(0, 0, width, height); + if (fabs(height*_statsWidth) <= fabs(width*_statsHeight)) + { + _camera->setProjectionMatrix(osg::Matrix::ortho2D(_statsWidth - width*_statsHeight/height, _statsWidth,0.0,_statsHeight)); + } + else + { + _camera->setProjectionMatrix(osg::Matrix::ortho2D(0.0,_statsWidth,_statsHeight-height*_statsWidth/width,_statsHeight)); + } + +} + +void StatsHandler::toggle(osgViewer::ViewerBase *viewer) +{ + if (!_initialized) + { + setUpHUDCamera(viewer); + setUpScene(viewer); + } + + _statsType = !_statsType; + + if (!_statsType) + { + _camera->setNodeMask(0); + _switch->setAllChildrenOff(); + + viewer->getViewerStats()->collectStats("resource", false); + } + else + { + _camera->setNodeMask(0xffffffff); + _switch->setSingleChildOn(_resourceStatsChildNum); + + viewer->getViewerStats()->collectStats("resource", true); + } +} + +void StatsHandler::setUpHUDCamera(osgViewer::ViewerBase* viewer) +{ + // Try GraphicsWindow first so we're likely to get the main viewer window + osg::GraphicsContext* context = dynamic_cast(_camera->getGraphicsContext()); + + if (!context) + { + osgViewer::Viewer::Windows windows; + viewer->getWindows(windows); + + if (!windows.empty()) context = windows.front(); + else + { + // No GraphicsWindows were found, so let's try to find a GraphicsContext + context = _camera->getGraphicsContext(); + + if (!context) + { + osgViewer::Viewer::Contexts contexts; + viewer->getContexts(contexts); + + if (contexts.empty()) return; + + context = contexts.front(); + } + } + } + + _camera->setGraphicsContext(context); + + _camera->setRenderOrder(osg::Camera::POST_RENDER, 11); + + _camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + _camera->setViewMatrix(osg::Matrix::identity()); + setWindowSize(context->getTraits()->width, context->getTraits()->height); + + // only clear the depth buffer + _camera->setClearMask(0); + _camera->setAllowEventFocus(false); + + _camera->setRenderer(new osgViewer::Renderer(_camera.get())); + + _initialized = true; +} + +osg::Geometry* createBackgroundRectangle(const osg::Vec3& pos, const float width, const float height, osg::Vec4& color) +{ + osg::StateSet *ss = new osg::StateSet; + + osg::Geometry* geometry = new osg::Geometry; + + geometry->setUseDisplayList(false); + geometry->setStateSet(ss); + + osg::Vec3Array* vertices = new osg::Vec3Array; + geometry->setVertexArray(vertices); + + vertices->push_back(osg::Vec3(pos.x(), pos.y(), 0)); + vertices->push_back(osg::Vec3(pos.x(), pos.y()-height,0)); + vertices->push_back(osg::Vec3(pos.x()+width, pos.y()-height,0)); + vertices->push_back(osg::Vec3(pos.x()+width, pos.y(),0)); + + osg::Vec4Array* colors = new osg::Vec4Array; + colors->push_back(color); + geometry->setColorArray(colors, osg::Array::BIND_OVERALL); + + osg::DrawElementsUShort *base = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_FAN,0); + base->push_back(0); + base->push_back(1); + base->push_back(2); + base->push_back(3); + + geometry->addPrimitiveSet(base); + + return geometry; +} + +class ResourceStatsTextDrawCallback : public osg::Drawable::DrawCallback +{ +public: + ResourceStatsTextDrawCallback(osg::Stats* stats, const std::vector& statNames) + : _stats(stats) + , _statNames(statNames) + { + } + + virtual void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* drawable) const + { + if (!_stats) return; + + osgText::Text* text = (osgText::Text*)(drawable); + + std::ostringstream viewStr; + viewStr.setf(std::ios::left, std::ios::adjustfield); + viewStr.width(14); + // Used fixed formatting, as scientific will switch to "...e+.." notation for + // large numbers of vertices/drawables/etc. + viewStr.setf(std::ios::fixed); + viewStr.precision(0); + + unsigned int frameNumber = renderInfo.getState()->getFrameStamp()->getFrameNumber()-1; + + for (std::vector::const_iterator it = _statNames.begin(); it != _statNames.end(); ++it) + { + if (it->empty()) + viewStr << std::endl; + else + { + double value = 0.0; + if (_stats->getAttribute(frameNumber, *it, value)) + viewStr << std::setw(8) << value << std::endl; + else + viewStr << std::setw(8) << "." << std::endl; + } + } + + text->setText(viewStr.str()); + + text->drawImplementation(renderInfo); + } + + osg::ref_ptr _stats; + std::vector _statNames; +}; + +void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) +{ + _switch = new osg::Switch; + + _camera->addChild(_switch); + + osg::StateSet* stateset = _switch->getOrCreateStateSet(); + stateset->setMode(GL_LIGHTING,osg::StateAttribute::OFF); + stateset->setMode(GL_BLEND,osg::StateAttribute::ON); + stateset->setMode(GL_DEPTH_TEST,osg::StateAttribute::OFF); +#ifdef OSG_GL1_AVAILABLE + stateset->setAttribute(new osg::PolygonMode(), osg::StateAttribute::PROTECTED); +#endif + + osg::Vec3 pos(_statsWidth-300.f, _statsHeight-500.0f,0.0f); + osg::Vec4 backgroundColor(0.0, 0.0, 0.0f, 0.3); + osg::Vec4 staticTextColor(1.0, 1.0, 0.0f, 1.0); + osg::Vec4 dynamicTextColor(1.0, 1.0, 1.0f, 1.0); + float backgroundMargin = 5; + float backgroundSpacing = 3; + + // resource stats + { + osg::Group* group = new osg::Group; + group->setCullingActive(false); + _resourceStatsChildNum = _switch->getNumChildren(); + _switch->addChild(group, false); + + const char* statNames[] = {"Compiling", "WorkQueue", "WorkThread", "", "Node", "Node Instance", "Shape", "Shape Instance", "Image", "Nif", "Keyframe", "Terrain Cell", "Terrain Texture", "", "UnrefQueue"}; + + int numLines = sizeof(statNames) / sizeof(statNames[0]); + + group->addChild(createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), + 10 * _characterSize + 2 * backgroundMargin, + numLines * _characterSize + 2 * backgroundMargin, + backgroundColor)); + + osg::ref_ptr staticText = new osgText::Text; + group->addChild( staticText.get() ); + staticText->setColor(staticTextColor); + staticText->setFont(_font); + staticText->setCharacterSize(_characterSize); + staticText->setPosition(pos); + + std::ostringstream viewStr; + viewStr.clear(); + viewStr.setf(std::ios::left, std::ios::adjustfield); + viewStr.width(14); + for (size_t i = 0; isetText(viewStr.str()); + + pos.x() += 10 * _characterSize + 2 * backgroundMargin + backgroundSpacing; + + group->addChild(createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), + 5 * _characterSize + 2 * backgroundMargin, + numLines * _characterSize + 2 * backgroundMargin, + backgroundColor)); + + osg::ref_ptr statsText = new osgText::Text; + group->addChild( statsText.get() ); + + statsText->setColor(dynamicTextColor); + statsText->setFont(_font); + statsText->setCharacterSize(_characterSize); + statsText->setPosition(pos); + statsText->setText(""); + statsText->setDrawCallback(new ResourceStatsTextDrawCallback(viewer->getViewerStats(), std::vector(statNames, statNames + numLines))); + } +} + + +void StatsHandler::getUsage(osg::ApplicationUsage &usage) const +{ + usage.addKeyboardMouseBinding(_key, "On screen resource usage stats."); +} + + + +} diff --git a/components/resource/stats.hpp b/components/resource/stats.hpp new file mode 100644 index 000000000..bd3890d9b --- /dev/null +++ b/components/resource/stats.hpp @@ -0,0 +1,59 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_STATS_H +#define OPENMW_COMPONENTS_RESOURCE_STATS_H + +#include + +namespace osgViewer +{ + class ViewerBase; +} + +namespace osg +{ + class Switch; +} + +namespace Resource +{ + + class StatsHandler : public osgGA::GUIEventHandler + { + public: + StatsHandler(); + + void setKey(int key) { _key = key; } + int getKey() const { return _key; } + + bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa); + + void setWindowSize(int w, int h); + + void toggle(osgViewer::ViewerBase* viewer); + + void setUpHUDCamera(osgViewer::ViewerBase* viewer); + void setUpScene(osgViewer::ViewerBase* viewer); + + /** Get the keyboard and mouse usage of this manipulator.*/ + virtual void getUsage(osg::ApplicationUsage& usage) const; + + private: + osg::ref_ptr _switch; + int _key; + osg::ref_ptr _camera; + bool _initialized; + bool _statsType; + + float _statsWidth; + float _statsHeight; + + std::string _font; + float _leftPos; + float _characterSize; + + int _resourceStatsChildNum; + + }; + +} + +#endif diff --git a/components/sceneutil/unrefqueue.cpp b/components/sceneutil/unrefqueue.cpp index 238da0971..7e5646ecb 100644 --- a/components/sceneutil/unrefqueue.cpp +++ b/components/sceneutil/unrefqueue.cpp @@ -44,4 +44,9 @@ namespace SceneUtil mWorkItem = new UnrefWorkItem; } + unsigned int UnrefQueue::getNumItems() const + { + return mWorkItem->mObjects.size(); + } + } diff --git a/components/sceneutil/unrefqueue.hpp b/components/sceneutil/unrefqueue.hpp index 4e5293956..4a2724927 100644 --- a/components/sceneutil/unrefqueue.hpp +++ b/components/sceneutil/unrefqueue.hpp @@ -23,6 +23,8 @@ namespace SceneUtil /// Call from the main thread. void flush(SceneUtil::WorkQueue* workQueue); + unsigned int getNumItems() const; + private: osg::ref_ptr mWorkItem; }; diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp index 0685493d0..831c70a3f 100644 --- a/components/sceneutil/workqueue.cpp +++ b/components/sceneutil/workqueue.cpp @@ -100,6 +100,23 @@ osg::ref_ptr WorkQueue::removeWorkItem() return NULL; } +unsigned int WorkQueue::getNumItems() const +{ + OpenThreads::ScopedLock lock(mMutex); + return mQueue.size(); +} + +unsigned int WorkQueue::getNumActiveThreads() const +{ + unsigned int count = 0; + for (unsigned int i=0; iisActive()) + ++count; + } + return count; +} + WorkThread::WorkThread(WorkQueue *workQueue) : mWorkQueue(workQueue) { @@ -112,9 +129,16 @@ void WorkThread::run() osg::ref_ptr item = mWorkQueue->removeWorkItem(); if (!item) return; + mActive = true; item->doWork(); item->signalDone(); + mActive = false; } } +bool WorkThread::isActive() const +{ + return mActive; +} + } diff --git a/components/sceneutil/workqueue.hpp b/components/sceneutil/workqueue.hpp index 1b0e30be9..7de6a5af5 100644 --- a/components/sceneutil/workqueue.hpp +++ b/components/sceneutil/workqueue.hpp @@ -61,11 +61,15 @@ namespace SceneUtil /// @par Used internally by the WorkThread. osg::ref_ptr removeWorkItem(); + unsigned int getNumItems() const; + + unsigned int getNumActiveThreads() const; + private: bool mIsReleased; std::deque > mQueue; - OpenThreads::Mutex mMutex; + mutable OpenThreads::Mutex mMutex; OpenThreads::Condition mCondition; std::vector mThreads; @@ -79,8 +83,11 @@ namespace SceneUtil virtual void run(); + bool isActive() const; + private: WorkQueue* mWorkQueue; + volatile bool mActive; }; diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index e8a0823f8..bc7c0d78a 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -89,18 +89,16 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v if (!evt.key.repeat) mKeyboardListener->keyPressed(evt.key); - // temporary for the stats viewer - if (evt.key.keysym.sym == SDLK_F3) - mViewer->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_F3); + if (evt.key.keysym.sym >= SDLK_F1 && evt.key.keysym.sym <= SDLK_F12) + mViewer->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_F1 + (evt.key.keysym.sym - SDLK_F1)); break; case SDL_KEYUP: if (!evt.key.repeat) mKeyboardListener->keyReleased(evt.key); - // temporary for the stats viewer - if (evt.key.keysym.sym == SDLK_F3) - mViewer->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_F3); + if (evt.key.keysym.sym >= SDLK_F1 && evt.key.keysym.sym <= SDLK_F12) + mViewer->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_F1 + (evt.key.keysym.sym - SDLK_F1)); break; case SDL_TEXTEDITING: diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 231634e1e..6113178d3 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -314,4 +314,16 @@ void TerrainGrid::updateTextureFiltering() mResourceSystem->getSceneManager()->applyFilterSettings(it->second); } +void TerrainGrid::reportStats(unsigned int frameNumber, osg::Stats *stats) +{ + { + OpenThreads::ScopedLock lock(mGridCacheMutex); + stats->setAttribute(frameNumber, "Terrain Cell", mGridCache.size()); + } + { + OpenThreads::ScopedLock lock(mTextureCacheMutex); + stats->setAttribute(frameNumber, "Terrain Texture", mTextureCache.size()); + } +} + } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index defcce8b3..d619ed8f7 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -49,6 +49,8 @@ namespace Terrain /// @note Thread safe. void updateTextureFiltering(); + void reportStats(unsigned int frameNumber, osg::Stats *stats); + private: osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index f5638750c..cc2285e45 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -9,6 +9,7 @@ namespace osg { class Group; + class Stats; } namespace osgUtil @@ -43,6 +44,8 @@ namespace Terrain virtual void updateCache() {} + virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {} + float getHeightAt (const osg::Vec3f& worldPos); virtual osg::ref_ptr cacheCell(int x, int y) {return NULL;} diff --git a/docs/source/reference/modding/settings/cells.rst b/docs/source/reference/modding/settings/cells.rst index 6892bbff2..66baedbee 100644 --- a/docs/source/reference/modding/settings/cells.rst +++ b/docs/source/reference/modding/settings/cells.rst @@ -26,6 +26,8 @@ preload enabled Controls whether textures and objects will be pre-loaded in background threads. This setting being enabled should result in a reduced amount of loading screens, no impact on frame rate and a varying amount of additional RAM usage, depending on how the preloader was configured (see the below settings). The default preloading settings with vanilla game files should only use negligible amounts of RAM, however, when using high-res texture and model replacers it may be necessary to tweak these settings to prevent the game from running out of memory. +The effects of (pre-)loading can be observed on the in-game statistics panel brought up with the 'F4' key. + All settings starting with 'preload' in this section will have no effect if preloading is disabled, and can only be configured by editing the settings configuration file. From a5247394dcf22eeec64b57404501360dbc7a2af3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 22 Feb 2017 14:54:40 +0100 Subject: [PATCH 09/12] (Re)set the inventory listener outside of the Animation class --- apps/openmw/mwrender/actoranimation.cpp | 12 ++---------- apps/openmw/mwrender/actoranimation.hpp | 5 +---- apps/openmw/mwrender/animation.cpp | 5 +++++ apps/openmw/mwrender/animation.hpp | 2 ++ apps/openmw/mwrender/characterpreview.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 15 ++------------- apps/openmw/mwrender/npcanimation.hpp | 4 +--- apps/openmw/mwrender/objects.cpp | 23 ++++++++++++++++++++++- apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 8 +++++++- 10 files changed, 44 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 5dcd5212c..75e56e9e7 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -26,10 +26,8 @@ namespace MWRender { -ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, - bool disableListener) - : Animation(ptr, parentNode, resourceSystem), - mListenerDisabled(disableListener) +ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) + : Animation(ptr, parentNode, resourceSystem) { MWWorld::ContainerStore& store = mPtr.getClass().getContainerStore(mPtr); @@ -41,16 +39,10 @@ ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr addHiddenItemLight(*iter, light); } } - - if (!mListenerDisabled) - store.setContListener(this); } ActorAnimation::~ActorAnimation() { - if (!mListenerDisabled && mPtr.getRefData().getCustomData() && mPtr.getClass().getContainerStore(mPtr).getContListener() == this) - mPtr.getClass().getContainerStore(mPtr).setContListener(NULL); - for (ItemLightMap::iterator iter = mItemLights.begin(); iter != mItemLights.end(); ++iter) { mInsert->removeChild(iter->second); diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index 6922603e1..c00f05718 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -31,8 +31,7 @@ namespace MWRender class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener { public: - ActorAnimation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, - bool disableListener=false); + ActorAnimation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); virtual ~ActorAnimation(); virtual void itemAdded(const MWWorld::ConstPtr& item, int count); @@ -44,8 +43,6 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener typedef std::map > ItemLightMap; ItemLightMap mItemLights; - - bool mListenerDisabled; }; } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 30d3a049e..6f0f3426c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -464,6 +464,11 @@ namespace MWRender return mPtr; } + MWWorld::Ptr Animation::getPtr() + { + return mPtr; + } + void Animation::setActive(bool active) { if (mSkeleton) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index f765a7a40..d04760368 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -343,6 +343,8 @@ public: MWWorld::ConstPtr getPtr() const; + MWWorld::Ptr getPtr(); + /// Set active flag on the object skeleton, if one exists. /// @see SceneUtil::Skeleton::setActive void setActive(bool active); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 8c0ec3b80..68b5bbad8 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -220,7 +220,7 @@ namespace MWRender { mAnimation.reset(NULL); - mAnimation.reset(new NpcAnimation(mCharacter, mNode, mResourceSystem, true, true, + mAnimation.reset(new NpcAnimation(mCharacter, mNode, mResourceSystem, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal))); onSetup(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 8f87c46fb..b23edbe1c 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -269,13 +269,6 @@ const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap(); NpcAnimation::~NpcAnimation() { - if (!mListenerDisabled - // No need to getInventoryStore() to reset, if none exists - // This is to avoid triggering the listener via ensureCustomData()->autoEquip()->fireEquipmentChanged() - // all from within this destructor. ouch! - && mPtr.getRefData().getCustomData() && mPtr.getClass().getInventoryStore(mPtr).getInvListener() == this) - mPtr.getClass().getInventoryStore(mPtr).setInvListener(NULL, mPtr); - // do not detach (delete) parts yet, this is done so the background thread can handle the deletion for(size_t i = 0;i < ESM::PRT_Count;i++) { @@ -285,9 +278,8 @@ NpcAnimation::~NpcAnimation() } NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, - bool disableListener, bool disableSounds, ViewMode viewMode, float firstPersonFieldOfView) - : ActorAnimation(ptr, parentNode, resourceSystem, disableListener), - mListenerDisabled(disableListener), + bool disableSounds, ViewMode viewMode, float firstPersonFieldOfView) + : ActorAnimation(ptr, parentNode, resourceSystem), mViewMode(viewMode), mShowWeapons(false), mShowCarriedLeft(true), @@ -309,9 +301,6 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr par } updateNpcBase(); - - if (!disableListener) - mPtr.getClass().getInventoryStore(mPtr).setInvListener(this, mPtr); } void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 892851890..427a1baf8 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -38,8 +38,6 @@ public: private: static const PartBoneMap sPartList; - bool mListenerDisabled; - // Bounded Parts PartHolderPtr mObjectParts[ESM::PRT_Count]; std::string mSoundIds[ESM::PRT_Count]; @@ -105,7 +103,7 @@ public: * @param disableSounds Same as \a disableListener but for playing items sounds * @param viewMode */ - NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, bool disableListener = false, + NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, bool disableSounds = false, ViewMode viewMode=VM_Normal, float firstPersonFieldOfView=55.f); virtual ~NpcAnimation(); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 49c707d1d..23a342735 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -92,6 +92,8 @@ void Objects::insertCreature(const MWWorld::Ptr &ptr, const std::string &mesh, b else anim.reset(new CreatureAnimation(ptr, mesh, mResourceSystem)); + ptr.getClass().getContainerStore(ptr).setContListener(static_cast(anim.get())); + mObjects.insert(std::make_pair(ptr, anim.release())); } @@ -102,6 +104,9 @@ void Objects::insertNPC(const MWWorld::Ptr &ptr) std::auto_ptr anim (new NpcAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), mResourceSystem)); + ptr.getClass().getInventoryStore(ptr).setInvListener(anim.get(), ptr); + ptr.getClass().getInventoryStore(ptr).setContListener(anim.get()); + mObjects.insert(std::make_pair(ptr, anim.release())); } @@ -119,6 +124,13 @@ bool Objects::removeObject (const MWWorld::Ptr& ptr) delete iter->second; mObjects.erase(iter); + if (ptr.getClass().isNpc()) + { + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + store.setInvListener(NULL, ptr); + store.setContListener(NULL); + } + ptr.getRefData().getBaseNode()->getParent(0)->removeChild(ptr.getRefData().getBaseNode()); ptr.getRefData().setBaseNode(NULL); @@ -132,10 +144,19 @@ void Objects::removeCell(const MWWorld::CellStore* store) { for(PtrAnimationMap::iterator iter = mObjects.begin();iter != mObjects.end();) { - if(iter->first.getCell() == store) + MWWorld::Ptr ptr = iter->second->getPtr(); + if(ptr.getCell() == store) { if (mUnrefQueue.get()) mUnrefQueue->push(iter->second->getObjectRoot()); + + if (ptr.getClass().isNpc() && ptr.getRefData().getCustomData()) + { + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + store.setInvListener(NULL, ptr); + store.setContListener(NULL); + } + delete iter->second; mObjects.erase(iter++); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 09e1bb026..afdb50926 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -821,7 +821,7 @@ namespace MWRender void RenderingManager::renderPlayer(const MWWorld::Ptr &player) { - mPlayerAnimation.reset(new NpcAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, 0, false, NpcAnimation::VM_Normal, + mPlayerAnimation.reset(new NpcAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, 0, NpcAnimation::VM_Normal, mFirstPersonFieldOfView)); mCamera->setAnimation(mPlayerAnimation.get()); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3d9300264..9d3110a67 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -31,6 +31,7 @@ #include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors #include "../mwrender/animation.hpp" +#include "../mwrender/npcanimation.hpp" #include "../mwrender/renderingmanager.hpp" #include "../mwrender/camera.hpp" #include "../mwrender/vismask.hpp" @@ -2212,7 +2213,12 @@ namespace MWWorld { MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr()); - mRendering->renderPlayer(getPlayerPtr()); + MWWorld::Ptr player = getPlayerPtr(); + + mRendering->renderPlayer(player); + MWRender::NpcAnimation* anim = static_cast(mRendering->getAnimation(player)); + player.getClass().getInventoryStore(player).setInvListener(anim, player); + player.getClass().getInventoryStore(player).setContListener(anim); scaleObject(getPlayerPtr(), 1.f); // apply race height From 00d4fea91cc581a4410667331c69dfdda81f20b9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 22 Feb 2017 15:22:40 +0100 Subject: [PATCH 10/12] Derive Animation from osg::Referenced to allow the UnrefQueue to delete it --- apps/openmw/mwrender/animation.cpp | 5 ----- apps/openmw/mwrender/animation.hpp | 7 +++---- apps/openmw/mwrender/characterpreview.cpp | 6 +++--- apps/openmw/mwrender/characterpreview.hpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 6 ------ apps/openmw/mwrender/objects.cpp | 24 ++++++++++------------- apps/openmw/mwrender/objects.hpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 4 ++-- apps/openmw/mwrender/renderingmanager.hpp | 2 +- 9 files changed, 21 insertions(+), 37 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6f0f3426c..0a1d5eb09 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1673,9 +1673,4 @@ namespace MWRender } } - void PartHolder::unlink() - { - mNode = NULL; - } - } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index d04760368..27572544f 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -58,9 +58,6 @@ public: ~PartHolder(); - /// Unreferences mNode *without* detaching it from the graph. Only use if you know what you are doing. - void unlink(); - osg::ref_ptr getNode() { return mNode; @@ -74,7 +71,7 @@ private: }; typedef boost::shared_ptr PartHolderPtr; -class Animation +class Animation : public osg::Referenced { public: enum BoneGroup { @@ -339,6 +336,8 @@ protected: public: Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); + + /// Must be thread safe virtual ~Animation(); MWWorld::ConstPtr getPtr() const; diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 68b5bbad8..f412c1fac 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -218,10 +218,10 @@ namespace MWRender void CharacterPreview::rebuild() { - mAnimation.reset(NULL); + mAnimation = NULL; - mAnimation.reset(new NpcAnimation(mCharacter, mNode, mResourceSystem, true, - (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal))); + mAnimation = new NpcAnimation(mCharacter, mNode, mResourceSystem, true, + (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); onSetup(); diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index e6480e0b3..273bacedb 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -61,7 +61,7 @@ namespace MWRender MWWorld::Ptr mCharacter; - std::auto_ptr mAnimation; + osg::ref_ptr mAnimation; osg::ref_ptr mNode; std::string mCurrentAnimGroup; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index b23edbe1c..e95626c82 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -269,12 +269,6 @@ const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap(); NpcAnimation::~NpcAnimation() { - // do not detach (delete) parts yet, this is done so the background thread can handle the deletion - for(size_t i = 0;i < ESM::PRT_Count;i++) - { - if (mObjectParts[i].get()) - mObjectParts[i]->unlink(); - } } NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 23a342735..3b9317ea9 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -29,8 +29,6 @@ Objects::Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptrsecond; mObjects.clear(); for (CellMap::iterator iter = mCellSceneNodes.begin(); iter != mCellSceneNodes.end(); ++iter) @@ -74,9 +72,9 @@ void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool { insertBegin(ptr); - std::auto_ptr anim (new ObjectAnimation(ptr, mesh, mResourceSystem, animated, allowLight)); + osg::ref_ptr anim (new ObjectAnimation(ptr, mesh, mResourceSystem, animated, allowLight)); - mObjects.insert(std::make_pair(ptr, anim.release())); + mObjects.insert(std::make_pair(ptr, anim)); } void Objects::insertCreature(const MWWorld::Ptr &ptr, const std::string &mesh, bool weaponsShields) @@ -85,16 +83,16 @@ void Objects::insertCreature(const MWWorld::Ptr &ptr, const std::string &mesh, b ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); // CreatureAnimation - std::auto_ptr anim; + osg::ref_ptr anim; if (weaponsShields) - anim.reset(new CreatureWeaponAnimation(ptr, mesh, mResourceSystem)); + anim = new CreatureWeaponAnimation(ptr, mesh, mResourceSystem); else - anim.reset(new CreatureAnimation(ptr, mesh, mResourceSystem)); + anim = new CreatureAnimation(ptr, mesh, mResourceSystem); ptr.getClass().getContainerStore(ptr).setContListener(static_cast(anim.get())); - mObjects.insert(std::make_pair(ptr, anim.release())); + mObjects.insert(std::make_pair(ptr, anim)); } void Objects::insertNPC(const MWWorld::Ptr &ptr) @@ -102,12 +100,12 @@ void Objects::insertNPC(const MWWorld::Ptr &ptr) insertBegin(ptr); ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); - std::auto_ptr anim (new NpcAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), mResourceSystem)); + osg::ref_ptr anim (new NpcAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), mResourceSystem)); ptr.getClass().getInventoryStore(ptr).setInvListener(anim.get(), ptr); ptr.getClass().getInventoryStore(ptr).setContListener(anim.get()); - mObjects.insert(std::make_pair(ptr, anim.release())); + mObjects.insert(std::make_pair(ptr, anim)); } bool Objects::removeObject (const MWWorld::Ptr& ptr) @@ -119,9 +117,8 @@ bool Objects::removeObject (const MWWorld::Ptr& ptr) if(iter != mObjects.end()) { if (mUnrefQueue.get()) - mUnrefQueue->push(iter->second->getObjectRoot()); + mUnrefQueue->push(iter->second); - delete iter->second; mObjects.erase(iter); if (ptr.getClass().isNpc()) @@ -148,7 +145,7 @@ void Objects::removeCell(const MWWorld::CellStore* store) if(ptr.getCell() == store) { if (mUnrefQueue.get()) - mUnrefQueue->push(iter->second->getObjectRoot()); + mUnrefQueue->push(iter->second); if (ptr.getClass().isNpc() && ptr.getRefData().getCustomData()) { @@ -157,7 +154,6 @@ void Objects::removeCell(const MWWorld::CellStore* store) store.setContListener(NULL); } - delete iter->second; mObjects.erase(iter++); } else diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 5b91abea4..cc66ae20d 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -62,7 +62,7 @@ public: }; class Objects{ - typedef std::map PtrAnimationMap; + typedef std::map > PtrAnimationMap; typedef std::map > CellMap; CellMap mCellSceneNodes; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index afdb50926..5879c14c6 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -821,8 +821,8 @@ namespace MWRender void RenderingManager::renderPlayer(const MWWorld::Ptr &player) { - mPlayerAnimation.reset(new NpcAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, 0, NpcAnimation::VM_Normal, - mFirstPersonFieldOfView)); + mPlayerAnimation = new NpcAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, 0, NpcAnimation::VM_Normal, + mFirstPersonFieldOfView); mCamera->setAnimation(mPlayerAnimation.get()); mCamera->attachTo(player); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index fc72eb279..b1bb9fe0d 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -214,7 +214,7 @@ namespace MWRender std::auto_ptr mTerrain; std::auto_ptr mSky; std::auto_ptr mEffectManager; - std::auto_ptr mPlayerAnimation; + osg::ref_ptr mPlayerAnimation; osg::ref_ptr mPlayerNode; std::auto_ptr mCamera; osg::Vec3f mCurrentCameraPos; From d16322819984ebb81ca386c0e8f7e1cd05c6bd7f Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 22 Feb 2017 21:02:34 +0100 Subject: [PATCH 11/12] Fix updatePtr --- apps/openmw/mwrender/objects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 3b9317ea9..ab627b6f6 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -202,7 +202,7 @@ void Objects::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) PtrAnimationMap::iterator iter = mObjects.find(old); if(iter != mObjects.end()) { - Animation *anim = iter->second; + osg::ref_ptr anim = iter->second; mObjects.erase(iter); anim->updatePtr(cur); mObjects[cur] = anim; From d4ff1368370b67f70e7e60ce100835ba259e43cc Mon Sep 17 00:00:00 2001 From: David Cernat Date: Thu, 23 Feb 2017 01:05:45 +0200 Subject: [PATCH 12/12] [Client] Add comments about spellcasting as a result of merge conflict --- apps/openmw/mwmechanics/spellcasting.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 690945615..fde0869ef 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -819,6 +819,13 @@ namespace MWMechanics // Check success + // Major change done by tes3mp: + // Instead of checking whether the caster is a player or an NPC, + // check whether it's the LocalPlayer or a DedicatedPlayer and calculate + // calculate the success chance in clients' LocalPlayer::prepareAttack() + // TODO: Make this make sense for NPCs too + // TODO: See if LocalPlayer being the target and having godmode on + // can be accounted for like it is in OpenMW's corresponding code mwmp::DedicatedPlayer *dedicatedPlayer = mwmp::Players::getPlayer(mCaster); bool isDedicated = dedicatedPlayer != NULL;