mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 10:49:57 +00:00
1e4a854433
It was just adding a level of indirection to Ptr.getClass(). All the call were replaced by that instead. The number of lines changed is important, but the change itself is trivial, so everything should be fine. :)
834 lines
32 KiB
C++
834 lines
32 KiB
C++
#include "npcanimation.hpp"
|
|
|
|
#include <OgreSceneManager.h>
|
|
#include <OgreEntity.h>
|
|
#include <OgreParticleSystem.h>
|
|
#include <OgreSubEntity.h>
|
|
#include <OgreSkeleton.h>
|
|
#include <OgreSkeletonInstance.h>
|
|
#include <OgreSceneNode.h>
|
|
#include <OgreBone.h>
|
|
#include <OgreTechnique.h>
|
|
|
|
#include <extern/shiny/Main/Factory.hpp>
|
|
|
|
#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 <std::pair<std::string,int>, const ESM::BodyPart* > sVampireMapping;
|
|
|
|
std::pair<std::string, int> 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<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
|
|
for(MWWorld::Store<ESM::BodyPart>::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;
|
|
}
|
|
}
|
|
|
|
assert(sVampireMapping[thisCombination]);
|
|
return "meshes\\" + sVampireMapping[thisCombination]->mModel;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
namespace MWRender
|
|
{
|
|
|
|
float HeadAnimationTime::getValue() const
|
|
{
|
|
// TODO use time from text keys (Talk Start/Stop, Blink Start/Stop)
|
|
// TODO: Handle eye blinking
|
|
if (MWBase::Environment::get().getSoundManager()->sayDone(mReference))
|
|
return 0;
|
|
else
|
|
// TODO: Use the loudness of the currently playing sound
|
|
return 1;
|
|
}
|
|
|
|
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"));
|
|
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, ViewMode viewMode)
|
|
: Animation(ptr, node),
|
|
mVisibilityFlags(visibilityFlags),
|
|
mListenerDisabled(disableListener),
|
|
mViewMode(viewMode),
|
|
mShowWeapons(false),
|
|
mShowCarriedLeft(true),
|
|
mFirstPersonOffset(0.f, 0.f, 0.f),
|
|
mAlpha(1.f),
|
|
mNpcType(Type_Normal)
|
|
{
|
|
mNpc = mPtr.get<ESM::NPC>()->mBase;
|
|
|
|
mHeadAnimationTime = Ogre::SharedPtr<HeadAnimationTime>(new HeadAnimationTime(mPtr));
|
|
mWeaponAnimationTime = Ogre::SharedPtr<WeaponAnimationTime>(new WeaponAnimationTime(this));
|
|
|
|
for(size_t i = 0;i < ESM::PRT_Count;i++)
|
|
{
|
|
mPartslots[i] = -1; //each slot is empty
|
|
mPartPriorities[i] = 0;
|
|
}
|
|
|
|
if (!disableListener)
|
|
mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr);
|
|
|
|
updateNpcBase();
|
|
}
|
|
|
|
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<ESM::Race>().find(mNpc->mRace);
|
|
bool isWerewolf = (mNpcType == Type_Werewolf);
|
|
bool isVampire = (mNpcType == Type_Vampire);
|
|
|
|
if (isWerewolf)
|
|
{
|
|
mHeadModel = "meshes\\" + store.get<ESM::BodyPart>().find("WerewolfHead")->mModel;
|
|
mHairModel = "meshes\\" + store.get<ESM::BodyPart>().find("WerewolfHair")->mModel;
|
|
}
|
|
else
|
|
{
|
|
if (isVampire)
|
|
mHeadModel = getVampireHead(mNpc->mRace, mNpc->mFlags & ESM::NPC::Female);
|
|
else
|
|
mHeadModel = "meshes\\" + store.get<ESM::BodyPart>().find(mNpc->mHead)->mModel;
|
|
|
|
mHairModel = "meshes\\" + store.get<ESM::BodyPart>().find(mNpc->mHair)->mModel;
|
|
}
|
|
|
|
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");
|
|
setObjectRoot(smodel, true);
|
|
|
|
if(mViewMode != VM_FirstPerson)
|
|
{
|
|
addAnimSource(smodel);
|
|
if(!isWerewolf)
|
|
{
|
|
if(Misc::StringUtils::lowerCase(mNpc->mRace).find("argonian") != std::string::npos)
|
|
addAnimSource("meshes\\argonian_swimkna.nif");
|
|
else if(!mNpc->isMale() && !isBeast)
|
|
addAnimSource("meshes\\base_anim_female.nif");
|
|
if(mNpc->mModel.length() > 0)
|
|
addAnimSource("meshes\\"+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\\base_anim.1st.nif");
|
|
if(isBeast)
|
|
addAnimSource("meshes\\base_animkna.1st.nif");
|
|
if(!mNpc->isMale() && !isBeast)
|
|
addAnimSource("meshes\\base_anim_female.1st.nif");
|
|
}
|
|
}
|
|
|
|
for(size_t i = 0;i < ESM::PRT_Count;i++)
|
|
removeIndividualPart((ESM::PartReferenceType)i);
|
|
updateParts();
|
|
|
|
mWeaponAnimationTime->updateStartTime();
|
|
}
|
|
|
|
void NpcAnimation::updateParts()
|
|
{
|
|
mAlpha = 1.f;
|
|
const MWWorld::Class &cls = mPtr.getClass();
|
|
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
|
|
|
|
NpcType curType = Type_Normal;
|
|
if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).mMagnitude > 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]);
|
|
|
|
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<ESM::Clothing>()->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<ESM::Armor>()->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, ESM::PRT_RPauldron, ESM::PRT_LPauldron
|
|
};
|
|
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)
|
|
addOrReplaceIndividualPart(ESM::PRT_Head, -1,1, mHeadModel);
|
|
if(mPartPriorities[ESM::PRT_Hair] < 1 && mPartPriorities[ESM::PRT_Head] <= 1)
|
|
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<ESM::Light>()->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::string,int>,std::vector<const ESM::BodyPart*> > sRaceMapping;
|
|
|
|
static std::map <std::pair<std::string,int>, std::vector<const ESM::BodyPart*> > sVampireMapping;
|
|
|
|
static const int Flag_Female = 1<<0;
|
|
static const int Flag_FirstPerson = 1<<1;
|
|
|
|
bool isWerewolf = (mNpcType == Type_Werewolf);
|
|
int flags = (isWerewolf ? -1 : 0);
|
|
if(!mNpc->isMale())
|
|
flags |= Flag_Female;
|
|
if(mViewMode == VM_FirstPerson)
|
|
flags |= Flag_FirstPerson;
|
|
|
|
std::string race = (isWerewolf ? "werewolf" : Misc::StringUtils::lowerCase(mNpc->mRace));
|
|
std::pair<std::string, int> thisCombination = std::make_pair(race, flags);
|
|
if (sRaceMapping.find(thisCombination) == sRaceMapping.end())
|
|
{
|
|
typedef std::multimap<ESM::BodyPart::MeshPart,ESM::PartReferenceType> 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<const ESM::BodyPart*> &parts = sRaceMapping[thisCombination];
|
|
parts.resize(ESM::PRT_Count, NULL);
|
|
|
|
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
|
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
|
|
for(MWWorld::Store<ESM::BodyPart>::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 (!mNpc->isMale() != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female))
|
|
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;
|
|
}
|
|
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<const ESM::BodyPart*> &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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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, bool enchantedGlow, Ogre::Vector3* glowColor)
|
|
{
|
|
NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mSkelBase, bonename, 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);
|
|
|
|
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);
|
|
}
|
|
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<Ogre::Controller<Ogre::Real> >::iterator ctrl(mObjectParts[i]->mControllers.begin());
|
|
for(;ctrl != mObjectParts[i]->mControllers.end();ctrl++)
|
|
ctrl->update();
|
|
|
|
Ogre::Entity *ent = mObjectParts[i]->mSkelBase;
|
|
if(!ent) continue;
|
|
updateSkeletonInstance(baseinst, ent->getSkeleton());
|
|
ent->getAllAnimationStates()->_notifyDirty();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void NpcAnimation::removeIndividualPart(ESM::PartReferenceType type)
|
|
{
|
|
mPartPriorities[type] = 0;
|
|
mPartslots[type] = -1;
|
|
|
|
mObjectParts[type].setNull();
|
|
}
|
|
|
|
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;
|
|
|
|
mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type), enchantedGlow, glowColor);
|
|
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();
|
|
}
|
|
}
|
|
|
|
updateSkeletonInstance(mSkelBase->getSkeleton(), skel);
|
|
}
|
|
|
|
std::vector<Ogre::Controller<Ogre::Real> >::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);
|
|
else if (type == ESM::PRT_Weapon)
|
|
ctrl->setSource(mWeaponAnimationTime);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts, bool enchantedGlow, Ogre::Vector3* glowColor)
|
|
{
|
|
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
|
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
|
|
|
|
const char *ext = (mViewMode == VM_FirstPerson) ? ".1st" : "";
|
|
std::vector<ESM::PartReference>::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;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
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<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
|
{
|
|
MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
|
if (ammo != inv.end() && ammo->get<ESM::Weapon>()->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<ESM::Light>()->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<ESM::Static>().find (magicEffect->mHit);
|
|
bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
|
|
// Don't play particle VFX unless the effect is new or it should be looping.
|
|
if (isNew || loop)
|
|
addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
|
|
}
|
|
}
|
|
|
|
void NpcAnimation::setAlpha(float alpha)
|
|
{
|
|
if (alpha == mAlpha)
|
|
return;
|
|
mAlpha = alpha;
|
|
|
|
for (int i=0; i<ESM::PRT_Count; ++i)
|
|
{
|
|
if (mObjectParts[i].isNull())
|
|
continue;
|
|
|
|
for (unsigned int j=0; j<mObjectParts[i]->mEntities.size(); ++j)
|
|
{
|
|
Ogre::Entity* ent = mObjectParts[i]->mEntities[j];
|
|
if (ent != mObjectParts[i]->mSkelBase)
|
|
applyAlpha(alpha, ent, mObjectParts[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NpcAnimation::preRender(Ogre::Camera *camera)
|
|
{
|
|
Animation::preRender(camera);
|
|
for (int i=0; i<ESM::PRT_Count; ++i)
|
|
{
|
|
if (mObjectParts[i].isNull())
|
|
continue;
|
|
mObjectParts[i]->rotateBillboardNodes(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);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|