#include "npcanimation.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "renderconst.hpp" #include "camera.hpp" namespace { std::string getVampireHead(const std::string& race, bool female) { static std::map , const ESM::BodyPart* > sVampireMapping; std::pair thisCombination = std::make_pair(race, int(female)); if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store &partStore = store.get(); for(MWWorld::Store::iterator it = partStore.begin(); it != partStore.end(); ++it) { const ESM::BodyPart& bodypart = *it; if (!bodypart.mData.mVampire) continue; if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) continue; if (bodypart.mData.mPart != ESM::BodyPart::MP_Head) continue; if (female != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) continue; if (!Misc::StringUtils::ciEqual(bodypart.mRace, race)) continue; sVampireMapping[thisCombination] = &*it; } } if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) sVampireMapping[thisCombination] = NULL; const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination]; if (!bodyPart) return std::string(); return "meshes\\" + bodyPart->mModel; } bool isSkinned (NifOgre::ObjectScenePtr scene) { if (scene->mSkelBase == NULL) return false; for(size_t j = 0; j < scene->mEntities.size(); j++) { Ogre::Entity *ent = scene->mEntities[j]; if(scene->mSkelBase != ent && ent->hasSkeleton()) { return true; } } return false; } } namespace MWRender { HeadAnimationTime::HeadAnimationTime(MWWorld::Ptr reference) : mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0), mEnabled(true), mValue(0) { resetBlinkTimer(); } void HeadAnimationTime::setEnabled(bool enabled) { mEnabled = enabled; } void HeadAnimationTime::resetBlinkTimer() { mBlinkTimer = -(2.0f + OEngine::Misc::Rng::rollDice(6)); } void HeadAnimationTime::update(float dt) { if (!mEnabled) return; if (MWBase::Environment::get().getSoundManager()->sayDone(mReference)) { mBlinkTimer += dt; float duration = mBlinkStop - mBlinkStart; if (mBlinkTimer >= 0 && mBlinkTimer <= duration) { mValue = mBlinkStart + mBlinkTimer; } else mValue = mBlinkStop; if (mBlinkTimer > duration) resetBlinkTimer(); } else { mValue = mTalkStart + (mTalkStop - mTalkStart) * std::min(1.f, MWBase::Environment::get().getSoundManager()->getSaySoundLoudness(mReference)*2); // Rescale a bit (most voices are not very loud) } } float HeadAnimationTime::getValue() const { return mValue; } void HeadAnimationTime::setTalkStart(float value) { mTalkStart = value; } void HeadAnimationTime::setTalkStop(float value) { mTalkStop = value; } void HeadAnimationTime::setBlinkStart(float value) { mBlinkStart = value; } void HeadAnimationTime::setBlinkStop(float value) { mBlinkStop = value; } static NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; result.insert(std::make_pair(ESM::PRT_Head, "Head")); result.insert(std::make_pair(ESM::PRT_Hair, "Head")); // note it uses "Head" as attach bone, but "Hair" as filter result.insert(std::make_pair(ESM::PRT_Neck, "Neck")); result.insert(std::make_pair(ESM::PRT_Cuirass, "Chest")); result.insert(std::make_pair(ESM::PRT_Groin, "Groin")); result.insert(std::make_pair(ESM::PRT_Skirt, "Groin")); result.insert(std::make_pair(ESM::PRT_RHand, "Right Hand")); result.insert(std::make_pair(ESM::PRT_LHand, "Left Hand")); result.insert(std::make_pair(ESM::PRT_RWrist, "Right Wrist")); result.insert(std::make_pair(ESM::PRT_LWrist, "Left Wrist")); result.insert(std::make_pair(ESM::PRT_Shield, "Shield Bone")); result.insert(std::make_pair(ESM::PRT_RForearm, "Right Forearm")); result.insert(std::make_pair(ESM::PRT_LForearm, "Left Forearm")); result.insert(std::make_pair(ESM::PRT_RUpperarm, "Right Upper Arm")); result.insert(std::make_pair(ESM::PRT_LUpperarm, "Left Upper Arm")); result.insert(std::make_pair(ESM::PRT_RFoot, "Right Foot")); result.insert(std::make_pair(ESM::PRT_LFoot, "Left Foot")); result.insert(std::make_pair(ESM::PRT_RAnkle, "Right Ankle")); result.insert(std::make_pair(ESM::PRT_LAnkle, "Left Ankle")); result.insert(std::make_pair(ESM::PRT_RKnee, "Right Knee")); result.insert(std::make_pair(ESM::PRT_LKnee, "Left Knee")); result.insert(std::make_pair(ESM::PRT_RLeg, "Right Upper Leg")); result.insert(std::make_pair(ESM::PRT_LLeg, "Left Upper Leg")); result.insert(std::make_pair(ESM::PRT_RPauldron, "Right Clavicle")); result.insert(std::make_pair(ESM::PRT_LPauldron, "Left Clavicle")); result.insert(std::make_pair(ESM::PRT_Weapon, "Weapon Bone")); result.insert(std::make_pair(ESM::PRT_Tail, "Tail")); return result; } const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap(); NpcAnimation::~NpcAnimation() { if (!mListenerDisabled) mPtr.getClass().getInventoryStore(mPtr).setListener(NULL, mPtr); } NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, bool disableListener, bool disableSounds, ViewMode viewMode) : Animation(ptr, node), mListenerDisabled(disableListener), mViewMode(viewMode), mShowWeapons(false), mShowCarriedLeft(true), mNpcType(Type_Normal), mVisibilityFlags(visibilityFlags), mFirstPersonOffset(0.f, 0.f, 0.f), mAlpha(1.f), mSoundsDisabled(disableSounds), mHeadYaw(0.f), mHeadPitch(0.f) { mNpc = mPtr.get()->mBase; mHeadAnimationTime = Ogre::SharedPtr(new HeadAnimationTime(mPtr)); mWeaponAnimationTime = Ogre::SharedPtr(new WeaponAnimationTime(this)); for(size_t i = 0;i < ESM::PRT_Count;i++) { mPartslots[i] = -1; //each slot is empty mPartPriorities[i] = 0; } updateNpcBase(); if (!disableListener) mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr); } void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) { assert(viewMode != VM_HeadOnly); if(mViewMode == viewMode) return; mViewMode = viewMode; rebuild(); } void NpcAnimation::rebuild() { updateNpcBase(); MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr); } void NpcAnimation::updateNpcBase() { clearAnimSources(); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mNpc->mRace); bool isWerewolf = (mNpcType == Type_Werewolf); bool isVampire = (mNpcType == Type_Vampire); if (isWerewolf) { mHeadModel = "meshes\\" + store.get().find("WerewolfHead")->mModel; mHairModel = "meshes\\" + store.get().find("WerewolfHair")->mModel; } else { mHeadModel = ""; if (isVampire) // FIXME: fall back to regular head when getVampireHead fails? mHeadModel = getVampireHead(mNpc->mRace, mNpc->mFlags & ESM::NPC::Female); else if (!mNpc->mHead.empty()) { const ESM::BodyPart* bp = store.get().search(mNpc->mHead); if (bp) mHeadModel = "meshes\\" + bp->mModel; else std::cerr << "Failed to load body part '" << mNpc->mHead << "'" << std::endl; } mHairModel = ""; if (!mNpc->mHair.empty()) { const ESM::BodyPart* bp = store.get().search(mNpc->mHair); if (bp) mHairModel = "meshes\\" + bp->mModel; else std::cerr << "Failed to load body part '" << mNpc->mHair << "'" << std::endl; } } bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; std::string smodel = (mViewMode != VM_FirstPerson) ? (!isWerewolf ? !isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif" : "meshes\\wolf\\skin.nif") : (!isWerewolf ? !isBeast ? "meshes\\base_anim.1st.nif" : "meshes\\base_animkna.1st.nif" : "meshes\\wolf\\skin.1st.nif"); smodel = Misc::ResourceHelpers::correctActorModelPath(smodel); setObjectRoot(smodel, true); if(mViewMode != VM_FirstPerson) { addAnimSource(smodel); if(!isWerewolf) { if(Misc::StringUtils::lowerCase(mNpc->mRace).find("argonian") != std::string::npos) addAnimSource("meshes\\xargonian_swimkna.nif"); else if(!mNpc->isMale() && !isBeast) addAnimSource("meshes\\xbase_anim_female.nif"); if(mNpc->mModel.length() > 0) addAnimSource("meshes\\x"+mNpc->mModel); } } else { if(isWerewolf) addAnimSource(smodel); else { /* A bit counter-intuitive, but unlike third-person anims, it seems * beast races get both base_anim.1st.nif and base_animkna.1st.nif. */ addAnimSource("meshes\\xbase_anim.1st.nif"); if(isBeast) addAnimSource("meshes\\xbase_animkna.1st.nif"); if(!mNpc->isMale() && !isBeast) addAnimSource("meshes\\xbase_anim_female.1st.nif"); } } for(size_t i = 0;i < ESM::PRT_Count;i++) removeIndividualPart((ESM::PartReferenceType)i); updateParts(); mWeaponAnimationTime->updateStartTime(); } void NpcAnimation::updateParts() { if (!mSkelBase) return; mAlpha = 1.f; const MWWorld::Class &cls = mPtr.getClass(); NpcType curType = Type_Normal; if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0) curType = Type_Vampire; if (cls.getNpcStats(mPtr).isWerewolf()) curType = Type_Werewolf; if (curType != mNpcType) { mNpcType = curType; rebuild(); return; } static const struct { int mSlot; int mBasePriority; } slotlist[] = { // FIXME: Priority is based on the number of reserved slots. There should be a better way. { MWWorld::InventoryStore::Slot_Robe, 12 }, { MWWorld::InventoryStore::Slot_Skirt, 3 }, { MWWorld::InventoryStore::Slot_Helmet, 0 }, { MWWorld::InventoryStore::Slot_Cuirass, 0 }, { MWWorld::InventoryStore::Slot_Greaves, 0 }, { MWWorld::InventoryStore::Slot_LeftPauldron, 0 }, { MWWorld::InventoryStore::Slot_RightPauldron, 0 }, { MWWorld::InventoryStore::Slot_Boots, 0 }, { MWWorld::InventoryStore::Slot_LeftGauntlet, 0 }, { MWWorld::InventoryStore::Slot_RightGauntlet, 0 }, { MWWorld::InventoryStore::Slot_Shirt, 0 }, { MWWorld::InventoryStore::Slot_Pants, 0 }, { MWWorld::InventoryStore::Slot_CarriedLeft, 0 }, { MWWorld::InventoryStore::Slot_CarriedRight, 0 } }; static const size_t slotlistsize = sizeof(slotlist)/sizeof(slotlist[0]); bool wasArrowAttached = (mAmmunition.get() != NULL); MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); for(size_t i = 0;i < slotlistsize && mViewMode != VM_HeadOnly;i++) { MWWorld::ContainerStoreIterator store = inv.getSlot(slotlist[i].mSlot); removePartGroup(slotlist[i].mSlot); if(store == inv.end()) continue; if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Helmet) removeIndividualPart(ESM::PRT_Hair); int prio = 1; bool enchantedGlow = !store->getClass().getEnchantment(*store).empty(); Ogre::Vector3 glowColor = getEnchantmentColor(*store); if(store->getTypeName() == typeid(ESM::Clothing).name()) { prio = ((slotlist[i].mBasePriority+1)<<1) + 0; const ESM::Clothing *clothes = store->get()->mBase; addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts, enchantedGlow, &glowColor); } else if(store->getTypeName() == typeid(ESM::Armor).name()) { prio = ((slotlist[i].mBasePriority+1)<<1) + 1; const ESM::Armor *armor = store->get()->mBase; addPartGroup(slotlist[i].mSlot, prio, armor->mParts.mParts, enchantedGlow, &glowColor); } if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Robe) { ESM::PartReferenceType parts[] = { ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, ESM::PRT_RForearm, ESM::PRT_LForearm }; size_t parts_size = sizeof(parts)/sizeof(parts[0]); for(size_t p = 0;p < parts_size;++p) reserveIndividualPart(parts[p], slotlist[i].mSlot, prio); } else if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Skirt) { reserveIndividualPart(ESM::PRT_Groin, slotlist[i].mSlot, prio); reserveIndividualPart(ESM::PRT_RLeg, slotlist[i].mSlot, prio); reserveIndividualPart(ESM::PRT_LLeg, slotlist[i].mSlot, prio); } } if(mViewMode != VM_FirstPerson) { if(mPartPriorities[ESM::PRT_Head] < 1 && !mHeadModel.empty()) addOrReplaceIndividualPart(ESM::PRT_Head, -1,1, mHeadModel); if(mPartPriorities[ESM::PRT_Hair] < 1 && mPartPriorities[ESM::PRT_Head] <= 1 && !mHairModel.empty()) addOrReplaceIndividualPart(ESM::PRT_Hair, -1,1, mHairModel); } if(mViewMode == VM_HeadOnly) return; if(mPartPriorities[ESM::PRT_Shield] < 1) { MWWorld::ContainerStoreIterator store = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); MWWorld::Ptr part; if(store != inv.end() && (part=*store).getTypeName() == typeid(ESM::Light).name()) { const ESM::Light *light = part.get()->mBase; addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, "meshes\\"+light->mModel); addExtraLight(mInsert->getCreator(), mObjectParts[ESM::PRT_Shield], light); } } showWeapons(mShowWeapons); showCarriedLeft(mShowCarriedLeft); // Remember body parts so we only have to search through the store once for each race/gender/viewmode combination static std::map< std::pair,std::vector > sRaceMapping; bool isWerewolf = (mNpcType == Type_Werewolf); int flags = (isWerewolf ? -1 : 0); if(!mNpc->isMale()) { static const int Flag_Female = 1<<0; flags |= Flag_Female; } if(mViewMode == VM_FirstPerson) { static const int Flag_FirstPerson = 1<<1; flags |= Flag_FirstPerson; } std::string race = (isWerewolf ? "werewolf" : Misc::StringUtils::lowerCase(mNpc->mRace)); std::pair thisCombination = std::make_pair(race, flags); if (sRaceMapping.find(thisCombination) == sRaceMapping.end()) { typedef std::multimap BodyPartMapType; static BodyPartMapType sBodyPartMap; if(sBodyPartMap.empty()) { sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Neck, ESM::PRT_Neck)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Groin, ESM::PRT_Groin)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_RHand)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_LHand)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_RFoot)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_LFoot)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_RKnee)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_LKnee)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg)); sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Tail, ESM::PRT_Tail)); } std::vector &parts = sRaceMapping[thisCombination]; parts.resize(ESM::PRT_Count, NULL); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store &partStore = store.get(); for(MWWorld::Store::iterator it = partStore.begin(); it != partStore.end(); ++it) { if(isWerewolf) break; const ESM::BodyPart& bodypart = *it; if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable) continue; if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) continue; if (!Misc::StringUtils::ciEqual(bodypart.mRace, mNpc->mRace)) continue; bool firstPerson = (bodypart.mId.size() >= 3) && bodypart.mId[bodypart.mId.size()-3] == '1' && bodypart.mId[bodypart.mId.size()-2] == 's' && bodypart.mId[bodypart.mId.size()-1] == 't'; if(firstPerson != (mViewMode == VM_FirstPerson)) { if(mViewMode == VM_FirstPerson && (bodypart.mData.mPart == ESM::BodyPart::MP_Hand || bodypart.mData.mPart == ESM::BodyPart::MP_Wrist || bodypart.mData.mPart == ESM::BodyPart::MP_Forearm || bodypart.mData.mPart == ESM::BodyPart::MP_Upperarm)) { /* Allow 3rd person skins as a fallback for the arms if 1st person is missing. */ BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) { if(!parts[bIt->second]) parts[bIt->second] = &*it; ++bIt; } } continue; } if (!mNpc->isMale() != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) { // Allow opposite gender's parts as fallback if parts for our gender are missing BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) { if(!parts[bIt->second]) parts[bIt->second] = &*it; ++bIt; } continue; } BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) { parts[bIt->second] = &*it; ++bIt; } } } const std::vector &parts = sRaceMapping[thisCombination]; for(int part = ESM::PRT_Neck; part < ESM::PRT_Count; ++part) { if(mPartPriorities[part] < 1) { const ESM::BodyPart* bodypart = parts[part]; if(bodypart) addOrReplaceIndividualPart((ESM::PartReferenceType)part, -1, 1, "meshes\\"+bodypart->mModel); } } if (wasArrowAttached) attachArrow(); } void NpcAnimation::addFirstPersonOffset(const Ogre::Vector3 &offset) { mFirstPersonOffset += offset; } class SetObjectGroup { int mGroup; public: SetObjectGroup(int group) : mGroup(group) { } void operator()(Ogre::MovableObject *obj) const { obj->getUserObjectBindings().setUserAny(Ogre::Any(mGroup)); } }; NifOgre::ObjectScenePtr NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename, const std::string &bonefilter, bool enchantedGlow, Ogre::Vector3* glowColor) { NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mSkelBase, bonename, bonefilter, mInsert, model); setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, enchantedGlow, glowColor); std::for_each(objects->mEntities.begin(), objects->mEntities.end(), SetObjectGroup(group)); std::for_each(objects->mParticles.begin(), objects->mParticles.end(), SetObjectGroup(group)); if(objects->mSkelBase) { Ogre::AnimationStateSet *aset = objects->mSkelBase->getAllAnimationStates(); Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator(); while(asiter.hasMoreElements()) { Ogre::AnimationState *state = asiter.getNext(); state->setEnabled(false); state->setLoop(false); } Ogre::SkeletonInstance *skelinst = objects->mSkelBase->getSkeleton(); Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator(); while(boneiter.hasMoreElements()) boneiter.getNext()->setManuallyControlled(true); } return objects; } Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) { Ogre::Vector3 ret = Animation::runAnimation(timepassed); mHeadAnimationTime->update(timepassed); if (mSkelBase) { Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); if(mViewMode == VM_FirstPerson) { float pitch = mPtr.getRefData().getPosition().rot[0]; Ogre::Node *node = baseinst->getBone("Bip01 Neck"); node->pitch(Ogre::Radian(-pitch), Ogre::Node::TS_WORLD); // This has to be done before this function ends; // updateSkeletonInstance, below, touches the hands. node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD); } else { // In third person mode we may still need pitch for ranged weapon targeting pitchSkeleton(mPtr.getRefData().getPosition().rot[0], baseinst); Ogre::Node* node = baseinst->getBone("Bip01 Head"); if (node) node->rotate(Ogre::Quaternion(mHeadYaw, Ogre::Vector3::UNIT_Z) * Ogre::Quaternion(mHeadPitch, Ogre::Vector3::UNIT_X), Ogre::Node::TS_WORLD); } } mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. for(size_t i = 0;i < ESM::PRT_Count;i++) { if (mObjectParts[i].isNull()) continue; std::vector >::iterator ctrl(mObjectParts[i]->mControllers.begin()); for(;ctrl != mObjectParts[i]->mControllers.end();++ctrl) ctrl->update(); if (!isSkinned(mObjectParts[i])) continue; if (mSkelBase) updateSkeletonInstance(mSkelBase->getSkeleton(), mObjectParts[i]->mSkelBase->getSkeleton()); mObjectParts[i]->mSkelBase->getAllAnimationStates()->_notifyDirty(); } return ret; } void NpcAnimation::removeIndividualPart(ESM::PartReferenceType type) { mPartPriorities[type] = 0; mPartslots[type] = -1; mObjectParts[type].setNull(); if (!mSoundIds[type].empty() && !mSoundsDisabled) { MWBase::Environment::get().getSoundManager()->stopSound3D(mPtr, mSoundIds[type]); mSoundIds[type].clear(); } } void NpcAnimation::reserveIndividualPart(ESM::PartReferenceType type, int group, int priority) { if(priority > mPartPriorities[type]) { removeIndividualPart(type); mPartPriorities[type] = priority; mPartslots[type] = group; } } void NpcAnimation::removePartGroup(int group) { for(int i = 0; i < ESM::PRT_Count; i++) { if(mPartslots[i] == group) removeIndividualPart((ESM::PartReferenceType)i); } } bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow, Ogre::Vector3* glowColor) { if(priority <= mPartPriorities[type]) return false; removeIndividualPart(type); mPartslots[type] = group; mPartPriorities[type] = priority; try { const std::string& bonename = sPartList.at(type); // PRT_Hair seems to be the only type that breaks consistency and uses a filter that's different from the attachment bone const std::string bonefilter = (type == ESM::PRT_Hair) ? "hair" : bonename; mObjectParts[type] = insertBoundedPart(mesh, group, bonename, bonefilter, enchantedGlow, glowColor); } catch (std::exception& e) { std::cerr << "Error adding NPC part: " << e.what() << std::endl; return false; } if (!mSoundsDisabled) { MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ContainerStoreIterator csi = inv.getSlot(group < 0 ? MWWorld::InventoryStore::Slot_Helmet : group); if (csi != inv.end()) { mSoundIds[type] = csi->getClass().getSound(*csi); if (!mSoundIds[type].empty()) { MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, mSoundIds[type], 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); } } } if(mObjectParts[type]->mSkelBase) { Ogre::SkeletonInstance *skel = mObjectParts[type]->mSkelBase->getSkeleton(); if(mObjectParts[type]->mSkelBase->isParentTagPoint()) { Ogre::Node *root = mObjectParts[type]->mSkelBase->getParentNode(); if(skel->hasBone("BoneOffset")) { Ogre::Bone *offset = skel->getBone("BoneOffset"); root->translate(offset->getPosition()); // It appears that the BoneOffset rotation is completely bogus, at least for light models. //root->rotate(offset->getOrientation()); root->pitch(Ogre::Degree(-90.0f)); root->scale(offset->getScale()); root->setInitialState(); } } if (isSkinned(mObjectParts[type])) updateSkeletonInstance(mSkelBase->getSkeleton(), skel); } std::vector >::iterator ctrl(mObjectParts[type]->mControllers.begin()); for(;ctrl != mObjectParts[type]->mControllers.end();++ctrl) { if(ctrl->getSource().isNull()) { ctrl->setSource(mNullAnimationTimePtr); if (type == ESM::PRT_Head) { ctrl->setSource(mHeadAnimationTime); const NifOgre::TextKeyMap& keys = mObjectParts[type]->mTextKeys; for (NifOgre::TextKeyMap::const_iterator it = keys.begin(); it != keys.end(); ++it) { if (Misc::StringUtils::ciEqual(it->second, "talk: start")) mHeadAnimationTime->setTalkStart(it->first); if (Misc::StringUtils::ciEqual(it->second, "talk: stop")) mHeadAnimationTime->setTalkStop(it->first); if (Misc::StringUtils::ciEqual(it->second, "blink: start")) mHeadAnimationTime->setBlinkStart(it->first); if (Misc::StringUtils::ciEqual(it->second, "blink: stop")) mHeadAnimationTime->setBlinkStop(it->first); } } else if (type == ESM::PRT_Weapon) ctrl->setSource(mWeaponAnimationTime); } } return true; } void NpcAnimation::addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow, Ogre::Vector3* glowColor) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store &partStore = store.get(); const char *ext = (mViewMode == VM_FirstPerson) ? ".1st" : ""; std::vector::const_iterator part(parts.begin()); for(;part != parts.end();++part) { const ESM::BodyPart *bodypart = 0; if(!mNpc->isMale() && !part->mFemale.empty()) { bodypart = partStore.search(part->mFemale+ext); if(!bodypart && mViewMode == VM_FirstPerson) { bodypart = partStore.search(part->mFemale); if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) bodypart = NULL; } else if (!bodypart) std::cerr << "Failed to find body part '" << part->mFemale << "'" << std::endl; } if(!bodypart && !part->mMale.empty()) { bodypart = partStore.search(part->mMale+ext); if(!bodypart && mViewMode == VM_FirstPerson) { bodypart = partStore.search(part->mMale); if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) bodypart = NULL; } else if (!bodypart) std::cerr << "Failed to find body part '" << part->mMale << "'" << std::endl; } if(bodypart) addOrReplaceIndividualPart((ESM::PartReferenceType)part->mPart, group, priority, "meshes\\"+bodypart->mModel, enchantedGlow, glowColor); else reserveIndividualPart((ESM::PartReferenceType)part->mPart, group, priority); } } void NpcAnimation::showWeapons(bool showWeapon) { mShowWeapons = showWeapon; if(showWeapon) { MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon != inv.end()) { Ogre::Vector3 glowColor = getEnchantmentColor(*weapon); std::string mesh = weapon->getClass().getModel(*weapon); addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); // Crossbows start out with a bolt attached if (weapon->getTypeName() == typeid(ESM::Weapon).name() && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) attachArrow(); else mAmmunition.setNull(); } else mAmmunition.setNull(); } } else { removeIndividualPart(ESM::PRT_Weapon); } mAlpha = 1.f; } void NpcAnimation::showCarriedLeft(bool show) { mShowCarriedLeft = show; MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(show && iter != inv.end()) { Ogre::Vector3 glowColor = getEnchantmentColor(*iter); std::string mesh = iter->getClass().getModel(*iter); if (addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor)) { if (iter->getTypeName() == typeid(ESM::Light).name()) addExtraLight(mInsert->getCreator(), mObjectParts[ESM::PRT_Shield], iter->get()->mBase); } } else removeIndividualPart(ESM::PRT_Shield); } void NpcAnimation::configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) { Ogre::Vector3 glowColor = getEnchantmentColor(ptr); setRenderProperties(object, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, !ptr.getClass().getEnchantment(ptr).empty(), &glowColor); std::for_each(object->mEntities.begin(), object->mEntities.end(), SetObjectGroup(slot)); std::for_each(object->mParticles.begin(), object->mParticles.end(), SetObjectGroup(slot)); } void NpcAnimation::attachArrow() { WeaponAnimation::attachArrow(mPtr); } void NpcAnimation::releaseArrow() { WeaponAnimation::releaseArrow(mPtr); } 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().find (magicEffect->mHit); bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; // 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, ""); } } void NpcAnimation::setAlpha(float alpha) { if (alpha == mAlpha) return; mAlpha = alpha; for (int i=0; imEntities.size(); ++j) { Ogre::Entity* ent = mObjectParts[i]->mEntities[j]; if (ent != mObjectParts[i]->mSkelBase) applyAlpha(alpha, ent, mObjectParts[i]); } } } void NpcAnimation::enableHeadAnimation(bool enable) { mHeadAnimationTime->setEnabled(enable); } void NpcAnimation::preRender(Ogre::Camera *camera) { Animation::preRender(camera); for (int i=0; irotateBillboardNodes(camera); } } void NpcAnimation::applyAlpha(float alpha, Ogre::Entity *ent, NifOgre::ObjectScenePtr scene) { sh::Factory::getInstance()._ensureMaterial(ent->getSubEntity(0)->getMaterial()->getName(), "Default"); ent->getSubEntity(0)->setRenderQueueGroup(alpha != 1.f || ent->getSubEntity(0)->getMaterial()->isTransparent() ? RQG_Alpha : RQG_Main); Ogre::MaterialPtr mat = scene->mMaterialControllerMgr.getWritableMaterial(ent); if (mAlpha == 1.f) { // Don't bother remembering what the original values were. Just remove the techniques and let the factory restore them. mat->removeAllTechniques(); sh::Factory::getInstance()._ensureMaterial(mat->getName(), "Default"); return; } Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); while(techs.hasMoreElements()) { Ogre::Technique *tech = techs.getNext(); Ogre::Technique::PassIterator passes = tech->getPassIterator(); while(passes.hasMoreElements()) { Ogre::Pass *pass = passes.getNext(); pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); Ogre::ColourValue diffuse = pass->getDiffuse(); diffuse.a = alpha; pass->setDiffuse(diffuse); pass->setVertexColourTracking(pass->getVertexColourTracking() &~Ogre::TVC_DIFFUSE); } } } void NpcAnimation::equipmentChanged() { updateParts(); } void NpcAnimation::setVampire(bool vampire) { if (mNpcType == Type_Werewolf) // we can't have werewolf vampires, can we return; if ((mNpcType == Type_Vampire) != vampire) { if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) MWBase::Environment::get().getWorld()->reattachPlayerCamera(); else rebuild(); } } void NpcAnimation::setHeadPitch(Ogre::Radian pitch) { mHeadPitch = pitch; } void NpcAnimation::setHeadYaw(Ogre::Radian yaw) { mHeadYaw = yaw; } Ogre::Radian NpcAnimation::getHeadPitch() const { return mHeadPitch; } Ogre::Radian NpcAnimation::getHeadYaw() const { return mHeadYaw; } }