#include "actoranimation.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "vismask.hpp" namespace MWRender { ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) : Animation(ptr, parentNode, resourceSystem) { MWWorld::ContainerStore& store = mPtr.getClass().getContainerStore(mPtr); for (MWWorld::ConstContainerStoreIterator iter = store.cbegin(MWWorld::ContainerStore::Type_Light); iter != store.cend(); ++iter) { const ESM::Light* light = iter->get()->mBase; if (!(light->mData.mFlags & ESM::Light::Carry)) { addHiddenItemLight(*iter, light); } } // Make sure we cleaned object from effects, just in cast if we re-use node removeEffects(); mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); } ActorAnimation::~ActorAnimation() { for (ItemLightMap::iterator iter = mItemLights.begin(); iter != mItemLights.end(); ++iter) { mInsert->removeChild(iter->second); } mScabbard.reset(); } PartHolderPtr ActorAnimation::getWeaponPart(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor) { osg::Group* parent = getBoneByName(bonename); if (!parent) return nullptr; osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model, parent); const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); if (found == nodeMap.end()) return PartHolderPtr(); if (enchantedGlow) addGlow(instance, *glowColor); return PartHolderPtr(new PartHolder(instance)); } osg::Group* ActorAnimation::getBoneByName(std::string boneName) { if (!mObjectRoot) return nullptr; SceneUtil::FindByNameVisitor findVisitor (boneName); mObjectRoot->accept(findVisitor); return findVisitor.mFoundNode; } std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon) { std::string boneName; if(weapon.isEmpty()) return boneName; const std::string &type = weapon.getClass().getTypeName(); if(type == typeid(ESM::Weapon).name()) { const MWWorld::LiveCellRef *ref = weapon.get(); ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; return getHolsteredWeaponBoneName(weaponType); } return boneName; } std::string ActorAnimation::getHolsteredWeaponBoneName(const unsigned int weaponType) { std::string boneName; switch(weaponType) { case ESM::Weapon::ShortBladeOneHand: boneName = "Bip01 ShortBladeOneHand"; break; case ESM::Weapon::LongBladeOneHand: boneName = "Bip01 LongBladeOneHand"; break; case ESM::Weapon::BluntOneHand: boneName = "Bip01 BluntOneHand"; break; case ESM::Weapon::AxeOneHand: boneName = "Bip01 LongBladeOneHand"; break; case ESM::Weapon::LongBladeTwoHand: boneName = "Bip01 LongBladeTwoClose"; break; case ESM::Weapon::BluntTwoClose: boneName = "Bip01 BluntTwoClose"; break; case ESM::Weapon::AxeTwoHand: boneName = "Bip01 AxeTwoClose"; break; case ESM::Weapon::BluntTwoWide: boneName = "Bip01 BluntTwoWide"; break; case ESM::Weapon::SpearTwoWide: boneName = "Bip01 SpearTwoWide"; break; case ESM::Weapon::MarksmanBow: boneName = "Bip01 MarksmanBow"; break; case ESM::Weapon::MarksmanCrossbow: boneName = "Bip01 MarksmanCrossbow"; break; case ESM::Weapon::MarksmanThrown: boneName = "Bip01 MarksmanThrown"; break; default: break; } return boneName; } void ActorAnimation::injectWeaponBones() { if (!mResourceSystem->getVFS()->exists("meshes\\xbase_anim_sh.nif")) { mWeaponSheathing = false; return; } osg::ref_ptr sheathSkeleton = mResourceSystem->getSceneManager()->getInstance("meshes\\xbase_anim_sh.nif"); for (unsigned int type=0; type<=ESM::Weapon::MarksmanThrown; ++type) { const std::string holsteredBoneName = getHolsteredWeaponBoneName(type); SceneUtil::FindByNameVisitor findVisitor (holsteredBoneName); sheathSkeleton->accept(findVisitor); osg::ref_ptr sheathNode = findVisitor.mFoundNode; if (sheathNode && sheathNode.get()->getNumParents()) { osg::Group* sheathParent = getBoneByName(sheathNode.get()->getParent(0)->getName()); if (sheathParent) { sheathNode.get()->getParent(0)->removeChild(sheathNode); sheathParent->addChild(sheathNode); } } } } // To make sure we do not run morph controllers for weapons, i.e. bows class EmptyCallback : public osg::NodeCallback { public: virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { } }; void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) { if (!mWeaponSheathing) return; if (!mPtr.getClass().hasInventoryStore(mPtr)) return; mScabbard.reset(); const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) return; // Since throwing weapons stack themselves, do not show such weapon itself if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) showHolsteredWeapons = false; std::string mesh = weapon->getClass().getModel(*weapon); std::string scabbardName = mesh; std::string boneName = getHolsteredWeaponBoneName(*weapon); if (mesh.empty() || boneName.empty()) return; // If the scabbard is not found, use a weapon mesh as fallback scabbardName = scabbardName.replace(scabbardName.size()-4, 4, "_sh.nif"); bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); if(!mResourceSystem->getVFS()->exists(scabbardName)) { if (showHolsteredWeapons) { osg::Vec4f glowColor = getEnchantmentColor(*weapon); mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor); if (mScabbard) mScabbard->getNode()->setUpdateCallback(new EmptyCallback); } return; } mScabbard = getWeaponPart(scabbardName, boneName); osg::Group* weaponNode = getBoneByName("Bip01 Weapon"); if (!weaponNode) return; // When we draw weapon, hide the Weapon node from sheath model. // Otherwise add the enchanted glow to it. if (!showHolsteredWeapons) { weaponNode->setNodeMask(0); } else { // If mesh author declared empty weapon node, use transformation from this node, but use the common weapon mesh. // This approach allows to tweak weapon position without need to store the whole weapon mesh in the _sh file. if (!weaponNode->getNumChildren()) { osg::ref_ptr fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, weaponNode); fallbackNode->setUpdateCallback(new EmptyCallback); } if (isEnchanted) { osg::Vec4f glowColor = getEnchantmentColor(*weapon); addGlow(weaponNode, glowColor); } } } void ActorAnimation::updateQuiver() { if (!mWeaponSheathing) return; if (!mPtr.getClass().hasInventoryStore(mPtr)) return; const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) return; std::string mesh = weapon->getClass().getModel(*weapon); std::string boneName = getHolsteredWeaponBoneName(*weapon); if (mesh.empty() || boneName.empty()) return; osg::Group* ammoNode = getBoneByName("Bip01 Ammo"); if (!ammoNode) return; // Special case for throwing weapons - they do not use ammo, but they stack themselves bool suitableAmmo = false; MWWorld::ConstContainerStoreIterator ammo = weapon; unsigned int ammoCount = 0; if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) { ammoCount = ammo->getRefData().getCount(); osg::Group* throwingWeaponNode = getBoneByName("Weapon Bone"); if (throwingWeaponNode && throwingWeaponNode->getNumChildren()) ammoCount--; suitableAmmo = true; } else { ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (ammo == inv.end()) return; ammoCount = ammo->getRefData().getCount(); bool arrowAttached = isArrowAttached(); if (arrowAttached) ammoCount--; if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) suitableAmmo = ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt; else if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanBow) suitableAmmo = ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow; } if (ammoNode && suitableAmmo) { // We should not show more ammo than equipped and more than quiver mesh has ammoCount = std::min(ammoCount, ammoNode->getNumChildren()); // Remove existing ammo nodes for (unsigned int i=0; igetNumChildren(); ++i) { osg::ref_ptr arrowNode = ammoNode->getChild(i)->asGroup(); if (!arrowNode->getNumChildren()) continue; osg::ref_ptr arrowChildNode = arrowNode->getChild(0); arrowNode->removeChild(arrowChildNode); } // Add new ones osg::Vec4f glowColor = getEnchantmentColor(*ammo); std::string model = ammo->getClass().getModel(*ammo); for (unsigned int i=0; i arrowNode = ammoNode->getChild(i)->asGroup(); osg::ref_ptr arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode); if (!ammo->getClass().getEnchantment(*ammo).empty()) addGlow(arrow, glowColor); } } } void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/) { if (item.getTypeName() == typeid(ESM::Light).name()) { const ESM::Light* light = item.get()->mBase; if (!(light->mData.mFlags & ESM::Light::Carry)) { addHiddenItemLight(item, light); } } if (!mPtr.getClass().hasInventoryStore(mPtr)) return; // If the count of equipped ammo or throwing weapon was changed, we should update quiver const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) return; MWWorld::ConstContainerStoreIterator ammo = inv.end(); if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) ammo = weapon; else ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if(ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId()) updateQuiver(); } void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/) { if (item.getTypeName() == typeid(ESM::Light).name()) { ItemLightMap::iterator iter = mItemLights.find(item); if (iter != mItemLights.end()) { if (!item.getRefData().getCount()) { removeHiddenItemLight(item); } } } if (!mPtr.getClass().hasInventoryStore(mPtr)) return; // If the count of equipped ammo or throwing weapon was changed, we should update quiver const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) return; MWWorld::ConstContainerStoreIterator ammo = inv.end(); if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) ammo = weapon; else ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if(ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId()) updateQuiver(); } void ActorAnimation::addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight) { if (mItemLights.find(item) != mItemLights.end()) return; const Fallback::Map* fallback = MWBase::Environment::get().getWorld()->getFallback(); static bool outQuadInLin = fallback->getFallbackBool("LightAttenuation_OutQuadInLin"); static bool useQuadratic = fallback->getFallbackBool("LightAttenuation_UseQuadratic"); static float quadraticValue = fallback->getFallbackFloat("LightAttenuation_QuadraticValue"); static float quadraticRadiusMult = fallback->getFallbackFloat("LightAttenuation_QuadraticRadiusMult"); static bool useLinear = fallback->getFallbackBool("LightAttenuation_UseLinear"); static float linearRadiusMult = fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); static float linearValue = fallback->getFallbackFloat("LightAttenuation_LinearValue"); bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); osg::Vec4f ambient(1,1,1,1); osg::ref_ptr lightSource = SceneUtil::createLightSource(esmLight, Mask_Lighting, exterior, outQuadInLin, useQuadratic, quadraticValue, quadraticRadiusMult, useLinear, linearRadiusMult, linearValue, ambient); mInsert->addChild(lightSource); if (mLightListCallback && mPtr == MWMechanics::getPlayer()) mLightListCallback->getIgnoredLightSources().insert(lightSource.get()); mItemLights.insert(std::make_pair(item, lightSource)); } void ActorAnimation::removeHiddenItemLight(const MWWorld::ConstPtr& item) { ItemLightMap::iterator iter = mItemLights.find(item); if (iter == mItemLights.end()) return; if (mLightListCallback && mPtr == MWMechanics::getPlayer()) { std::set::iterator ignoredIter = mLightListCallback->getIgnoredLightSources().find(iter->second.get()); if (ignoredIter != mLightListCallback->getIgnoredLightSources().end()) mLightListCallback->getIgnoredLightSources().erase(ignoredIter); } mInsert->removeChild(iter->second); mItemLights.erase(iter); } }