1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-04 03:40:14 +00:00
OpenMW/apps/openmw/mwrender/npcanimation.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1307 lines
52 KiB
C++
Raw Normal View History

#include "npcanimation.hpp"
#include <osg/Depth>
#include <osg/MatrixTransform>
2015-04-19 14:42:09 +02:00
#include <osg/UserDataContainer>
#include <osgUtil/CullVisitor>
#include <osgUtil/RenderBin>
2015-05-31 01:07:43 +02:00
2018-08-14 23:05:43 +04:00
#include <components/debug/debuglog.hpp>
2015-04-22 17:58:55 +02:00
#include <components/misc/rng.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/esm3/loadbody.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadrace.hpp>
2015-04-15 22:11:38 +02:00
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/actorutil.hpp>
2021-11-21 02:25:05 +00:00
#include <components/sceneutil/depth.hpp>
#include <components/sceneutil/keyframe.hpp>
#include <components/sceneutil/lightcommon.hpp>
2015-05-31 01:07:43 +02:00
#include <components/sceneutil/visitor.hpp>
2023-06-27 23:41:06 +02:00
#include <components/settings/values.hpp>
#include <components/vfs/manager.hpp>
#include "../mwworld/class.hpp"
2012-10-01 19:17:04 +04:00
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
2015-08-21 21:12:39 +12:00
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/weapontype.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/world.hpp"
2015-05-31 18:53:16 +02:00
#include "postprocessor.hpp"
#include "renderbin.hpp"
#include "rotatecontroller.hpp"
#include "vismask.hpp"
2012-04-07 22:02:20 -04:00
namespace
{
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
std::string getVampireHead(const ESM::RefId& race, bool female, const VFS::Manager& vfs)
{
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
static std::map<std::pair<ESM::RefId, int>, const ESM::BodyPart*> sVampireMapping;
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
std::pair<ESM::RefId, int> thisCombination = std::make_pair(race, int(female));
if (sVampireMapping.find(thisCombination) == sVampireMapping.end())
{
2023-04-20 21:07:53 +02:00
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
2019-03-20 00:50:05 +03:00
for (const ESM::BodyPart& bodypart : store.get<ESM::BodyPart>())
2022-09-22 21:26:05 +03:00
{
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 (!(bodypart.mRace == race))
continue;
2019-03-20 00:50:05 +03:00
sVampireMapping[thisCombination] = &bodypart;
2022-09-22 21:26:05 +03:00
}
}
2019-03-20 00:50:05 +03:00
sVampireMapping.emplace(thisCombination, nullptr);
const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination];
if (!bodyPart)
return std::string();
return Misc::ResourceHelpers::correctMeshPath(bodyPart->mModel, &vfs);
}
}
namespace MWRender
{
class HeadAnimationTime : public SceneUtil::ControllerSource
{
private:
MWWorld::Ptr mReference;
float mTalkStart;
float mTalkStop;
float mBlinkStart;
float mBlinkStop;
float mBlinkTimer;
bool mEnabled;
float mValue;
private:
void resetBlinkTimer();
2022-09-22 21:26:05 +03:00
public:
HeadAnimationTime(const MWWorld::Ptr& reference);
void updatePtr(const MWWorld::Ptr& updated);
void update(float dt);
void setEnabled(bool enabled);
void setTalkStart(float value);
void setTalkStop(float value);
void setBlinkStart(float value);
void setBlinkStop(float value);
2015-04-25 13:37:42 -05:00
float getValue(osg::NodeVisitor* nv) override;
};
// --------------------------------------------------------------------------------------------------------------
2015-05-14 17:34:55 +02:00
HeadAnimationTime::HeadAnimationTime(const MWWorld::Ptr& reference)
: mReference(reference)
2015-04-25 13:37:42 -05:00
, mTalkStart(0)
, mTalkStop(0)
, mBlinkStart(0)
, mBlinkStop(0)
, mEnabled(true)
2022-09-22 21:26:05 +03:00
, mValue(0)
{
resetBlinkTimer();
}
void HeadAnimationTime::updatePtr(const MWWorld::Ptr& updated)
{
mReference = updated;
}
void HeadAnimationTime::setEnabled(bool enabled)
{
mEnabled = enabled;
2022-09-22 21:26:05 +03:00
}
void HeadAnimationTime::resetBlinkTimer()
{
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
mBlinkTimer = -(2.0f + Misc::Rng::rollDice(6, prng));
2022-09-22 21:26:05 +03:00
}
void HeadAnimationTime::update(float dt)
2022-09-22 21:26:05 +03:00
{
if (!mEnabled)
return;
if (!MWBase::Environment::get().getSoundManager()->sayActive(mReference))
{
mBlinkTimer += dt;
2022-09-22 21:26:05 +03:00
float duration = mBlinkStop - mBlinkStart;
2022-09-22 21:26:05 +03:00
if (mBlinkTimer >= 0 && mBlinkTimer <= duration)
2022-09-22 21:26:05 +03:00
{
mValue = mBlinkStart + mBlinkTimer;
2022-09-22 21:26:05 +03:00
}
else
mValue = mBlinkStop;
2022-09-22 21:26:05 +03:00
if (mBlinkTimer > duration)
resetBlinkTimer();
}
else
2022-09-22 21:26:05 +03:00
{
// FIXME: would be nice to hold on to the SoundPtr so we don't have to retrieve it every frame
mValue = mTalkStart
+ (mTalkStop - mTalkStart)
* std::min(1.f,
2014-07-29 14:19:12 +02:00
MWBase::Environment::get().getSoundManager()->getSaySoundLoudness(mReference)
* 2); // Rescale a bit (most voices are not very loud)
2022-09-22 21:26:05 +03:00
}
}
float HeadAnimationTime::getValue(osg::NodeVisitor*)
2022-09-22 21:26:05 +03:00
{
return mValue;
}
2022-09-22 21:26:05 +03:00
void HeadAnimationTime::setTalkStart(float value)
{
mTalkStart = value;
}
2015-04-19 14:42:09 +02:00
void HeadAnimationTime::setTalkStop(float value)
{
mTalkStop = value;
}
void HeadAnimationTime::setBlinkStart(float value)
{
mBlinkStart = value;
}
void HeadAnimationTime::setBlinkStop(float value)
{
mBlinkStop = value;
}
// ----------------------------------------------------
NpcAnimation::NpcType NpcAnimation::getNpcType() const
{
const MWWorld::Class& cls = mPtr.getClass();
// Dead vampires should typically stay vampires.
if (mNpcType == Type_Vampire && cls.getNpcStats(mPtr).isDead() && !cls.getNpcStats(mPtr).isWerewolf())
return mNpcType;
return getNpcType(mPtr);
}
NpcAnimation::NpcType NpcAnimation::getNpcType(const MWWorld::Ptr& ptr)
2022-09-22 21:26:05 +03:00
{
const MWWorld::Class& cls = ptr.getClass();
NpcAnimation::NpcType curType = Type_Normal;
2023-05-23 19:06:08 +02:00
if (cls.getCreatureStats(ptr).getMagicEffects().getOrDefault(ESM::MagicEffect::Vampirism).getMagnitude() > 0)
curType = Type_Vampire;
if (cls.getNpcStats(ptr).isWerewolf())
curType = Type_Werewolf;
2022-09-22 21:26:05 +03:00
return curType;
2022-09-22 21:26:05 +03:00
}
static const inline NpcAnimation::PartBoneMap createPartListMap()
2022-09-22 21:26:05 +03:00
{
return { { ESM::PRT_Head, "Head" },
{ ESM::PRT_Hair, "Head" }, // note it uses "Head" as attach bone, but "Hair" as filter
2013-04-25 23:42:20 -07:00
{ ESM::PRT_Neck, "Neck" }, { ESM::PRT_Cuirass, "Chest" }, { ESM::PRT_Groin, "Groin" },
{ ESM::PRT_Skirt, "Groin" }, { ESM::PRT_RHand, "Right Hand" }, { ESM::PRT_LHand, "Left Hand" },
{ ESM::PRT_RWrist, "Right Wrist" }, { ESM::PRT_LWrist, "Left Wrist" }, { ESM::PRT_Shield, "Shield Bone" },
2020-03-19 21:30:22 +01:00
{ ESM::PRT_RForearm, "Right Forearm" }, { ESM::PRT_LForearm, "Left Forearm" },
2013-04-25 23:42:20 -07:00
{ ESM::PRT_RUpperarm, "Right Upper Arm" }, { ESM::PRT_LUpperarm, "Left Upper Arm" },
{ ESM::PRT_RFoot, "Right Foot" }, { ESM::PRT_LFoot, "Left Foot" }, { ESM::PRT_RAnkle, "Right Ankle" },
{ ESM::PRT_LAnkle, "Left Ankle" }, { ESM::PRT_RKnee, "Right Knee" }, { ESM::PRT_LKnee, "Left Knee" },
{ ESM::PRT_RLeg, "Right Upper Leg" }, { ESM::PRT_LLeg, "Left Upper Leg" },
{ ESM::PRT_RPauldron, "Right Clavicle" }, { ESM::PRT_LPauldron, "Left Clavicle" },
{ ESM::PRT_Weapon, "Weapon Bone" }, // Fallback. The real node name depends on the current weapon type.
{ ESM::PRT_Tail, "Tail" } };
2022-09-22 21:26:05 +03:00
}
const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap();
NpcAnimation::~NpcAnimation()
{
mAmmunition.reset();
}
NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> parentNode,
Resource::ResourceSystem* resourceSystem, bool disableSounds, ViewMode viewMode, float firstPersonFieldOfView)
2023-07-29 12:58:27 +04:00
: ActorAnimation(ptr, std::move(parentNode), resourceSystem)
, mViewMode(viewMode)
, mShowWeapons(false)
, mShowCarriedLeft(true)
, mNpcType(getNpcType(ptr))
, mFirstPersonFieldOfView(firstPersonFieldOfView)
, mSoundsDisabled(disableSounds)
, mAccurateAiming(false)
, mAimingFactor(0.f)
{
mNpc = mPtr.get<ESM::NPC>()->mBase;
mHeadAnimationTime = std::make_shared<HeadAnimationTime>(mPtr);
mWeaponAnimationTime = std::make_shared<WeaponAnimationTime>(this);
for (size_t i = 0; i < ESM::PRT_Count; i++)
{
mPartslots[i] = -1; // each slot is empty
mPartPriorities[i] = 0;
2022-09-22 21:26:05 +03:00
}
std::fill(mSounds.begin(), mSounds.end(), nullptr);
2013-01-06 05:39:39 -08:00
updateNpcBase();
}
void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
{
assert(viewMode != VM_HeadOnly);
if (mViewMode == viewMode)
2022-09-22 21:26:05 +03:00
return;
// FIXME: sheathing state must be consistent if the third person skeleton doesn't have the necessary node, but
// third person skeleton is unavailable in first person view. This is a hack to avoid cosmetic issues.
bool viewChange = mViewMode == VM_FirstPerson || viewMode == VM_FirstPerson;
mViewMode = viewMode;
MWBase::Environment::get().getWorld()->scaleObject(
mPtr, mPtr.getCellRef().getScale(), true); // apply race height after view change
2022-09-22 21:26:05 +03:00
mAmmunition.reset();
rebuild();
setRenderBin();
2022-09-22 21:26:05 +03:00
2023-06-27 23:41:06 +02:00
if (viewChange && Settings::game().mShieldSheathing)
2022-09-22 21:26:05 +03:00
{
int weaptype = ESM::Weapon::None;
MWMechanics::getActiveWeapon(mPtr, &weaptype);
showCarriedLeft(updateCarriedLeftVisible(weaptype));
2022-09-22 21:26:05 +03:00
}
}
/// @brief A RenderBin callback to clear the depth buffer before rendering.
/// Switches depth attachments to a proxy renderbuffer, reattaches original depth then redraws first person root.
/// This gives a complete depth buffer which can be used for postprocessing, buffer resolves as if depth was never
/// cleared.
class DepthClearCallback : public osgUtil::RenderBin::DrawCallback
{
2022-09-22 21:26:05 +03:00
public:
2023-01-19 08:39:38 -08:00
DepthClearCallback(Resource::ResourceSystem* resourceSystem)
2022-09-22 21:26:05 +03:00
{
2023-01-19 08:39:38 -08:00
mPassNormals = resourceSystem->getSceneManager()->getSupportsNormalsRT();
2021-11-21 02:25:05 +00:00
mDepth = new SceneUtil::AutoDepth;
mDepth->setWriteMask(true);
mStateSet = new osg::StateSet;
mStateSet->setAttributeAndModes(new osg::ColorMask(false, false, false, false), osg::StateAttribute::ON);
mStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
}
void drawImplementation(
osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override
{
osg::State* state = renderInfo.getState();
PostProcessor* postProcessor = dynamic_cast<PostProcessor*>(renderInfo.getCurrentCamera()->getUserData());
state->applyAttribute(mDepth);
2022-05-13 18:58:00 -07:00
unsigned int frameId = state->getFrameStamp()->getFrameNumber() % 2;
2022-05-13 18:58:00 -07:00
if (postProcessor && postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId))
{
postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)->apply(*state);
2023-01-19 08:39:38 -08:00
if (mPassNormals)
{
state->get<osg::GLExtensions>()->glColorMaski(1, true, true, true, true);
state->haveAppliedAttribute(osg::StateAttribute::COLORMASK);
}
2022-02-07 11:51:59 -08:00
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// color accumulation pass
bin->drawImplementation(renderInfo, previous);
2022-05-13 18:58:00 -07:00
auto primaryFBO = postProcessor->getPrimaryFbo(frameId);
if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId))
postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state);
else
primaryFBO->apply(*state);
// depth accumulation pass
2022-05-13 18:58:00 -07:00
osg::ref_ptr<osg::StateSet> restore = bin->getStateSet();
bin->setStateSet(mStateSet);
bin->drawImplementation(renderInfo, previous);
2022-05-13 18:58:00 -07:00
bin->setStateSet(restore);
if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId))
primaryFBO->apply(*state);
}
else
{
// fallback to standard depth clear when we are not rendering our main scene via an intermediate FBO
2022-02-07 11:51:59 -08:00
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
bin->drawImplementation(renderInfo, previous);
}
2022-06-21 15:55:06 +00:00
state->checkGLErrors("after DepthClearCallback::drawImplementation");
}
2023-01-19 08:39:38 -08:00
bool mPassNormals;
osg::ref_ptr<osg::Depth> mDepth;
osg::ref_ptr<osg::StateSet> mStateSet;
};
/// Overrides Field of View to given value for rendering the subgraph.
/// Must be added as cull callback.
class OverrideFieldOfViewCallback : public osg::NodeCallback
{
2022-09-22 21:26:05 +03:00
public:
OverrideFieldOfViewCallback(float fov)
: mFov(fov)
2022-09-22 21:26:05 +03:00
{
}
void operator()(osg::Node* node, osg::NodeVisitor* nv) override
{
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
float fov, aspect, zNear, zFar;
2021-10-05 11:52:19 +00:00
if (cv->getProjectionMatrix()->getPerspective(fov, aspect, zNear, zFar) && std::abs(fov - mFov) > 0.001)
2022-09-22 21:26:05 +03:00
{
fov = mFov;
osg::ref_ptr<osg::RefMatrix> newProjectionMatrix = new osg::RefMatrix();
newProjectionMatrix->makePerspective(fov, aspect, zNear, zFar);
osg::ref_ptr<osg::RefMatrix> invertedOldMatrix = cv->getProjectionMatrix();
invertedOldMatrix = new osg::RefMatrix(osg::RefMatrix::inverse(*invertedOldMatrix));
osg::ref_ptr<osg::RefMatrix> viewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix());
viewMatrix->postMult(*newProjectionMatrix);
viewMatrix->postMult(*invertedOldMatrix);
cv->pushModelViewMatrix(viewMatrix, osg::Transform::ReferenceFrame::ABSOLUTE_RF);
traverse(node, nv);
cv->popModelViewMatrix();
2022-09-22 21:26:05 +03:00
}
else
traverse(node, nv);
}
private:
float mFov;
};
void NpcAnimation::setRenderBin()
{
if (mViewMode == VM_FirstPerson)
{
static bool prototypeAdded = false;
if (!prototypeAdded)
2022-09-22 21:26:05 +03:00
{
osg::ref_ptr<osgUtil::RenderBin> depthClearBin(new osgUtil::RenderBin);
2023-01-19 08:39:38 -08:00
depthClearBin->setDrawCallback(new DepthClearCallback(mResourceSystem));
osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin);
prototypeAdded = true;
2022-09-22 21:26:05 +03:00
}
mObjectRoot->getOrCreateStateSet()->setRenderBinDetails(
RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS);
}
else if (osg::StateSet* stateset = mObjectRoot->getStateSet())
stateset->setRenderBinToInherit();
}
void NpcAnimation::rebuild()
{
mScabbard.reset();
mHolsteredShield.reset();
2013-08-07 16:21:57 -07:00
updateNpcBase();
MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr);
}
2015-05-20 03:35:52 +02:00
int NpcAnimation::getSlot(const osg::NodePath& path) const
{
2022-04-08 22:04:32 +02:00
for (int i = 0; i < ESM::PRT_Count; ++i)
2015-05-20 03:35:52 +02:00
{
2022-04-08 22:04:32 +02:00
const PartHolder* const part = mObjectParts[i].get();
if (part == nullptr)
2015-05-20 03:35:52 +02:00
continue;
if (std::find(path.begin(), path.end(), part->getNode().get()) != path.end())
2022-09-22 21:26:05 +03:00
{
2015-05-20 03:35:52 +02:00
return mPartslots[i];
2022-09-22 21:26:05 +03:00
}
2015-05-20 03:35:52 +02:00
}
return -1;
}
2013-08-07 16:21:57 -07:00
void NpcAnimation::updateNpcBase()
{
2015-04-23 23:30:06 +02:00
clearAnimSources();
for (size_t i = 0; i < ESM::PRT_Count; i++)
removeIndividualPart((ESM::PartReferenceType)i);
2023-04-20 21:07:53 +02:00
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
const ESM::Race* race = store.get<ESM::Race>().find(mNpc->mRace);
NpcType curType = getNpcType();
bool isWerewolf = (curType == Type_Werewolf);
bool isVampire = (curType == Type_Vampire);
bool isFemale = !mNpc->isMale();
2019-03-20 00:50:05 +03:00
mHeadModel.clear();
mHairModel.clear();
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
const ESM::RefId& headName = isWerewolf ? ESM::RefId::stringRefId("WerewolfHead") : mNpc->mHead;
const ESM::RefId& hairName = isWerewolf ? ESM::RefId::stringRefId("WerewolfHair") : mNpc->mHair;
2019-03-20 00:50:05 +03:00
if (!headName.empty())
{
2019-03-20 00:50:05 +03:00
const ESM::BodyPart* bp = store.get<ESM::BodyPart>().search(headName);
if (bp)
mHeadModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel, mResourceSystem->getVFS());
2019-03-20 00:50:05 +03:00
else
Log(Debug::Warning) << "Warning: Failed to load body part '" << headName << "'";
}
2019-03-20 00:50:05 +03:00
if (!hairName.empty())
{
const ESM::BodyPart* bp = store.get<ESM::BodyPart>().search(hairName);
if (bp)
mHairModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel, mResourceSystem->getVFS());
2019-03-20 00:50:05 +03:00
else
Log(Debug::Warning) << "Warning: Failed to load body part '" << hairName << "'";
}
const std::string vampireHead = getVampireHead(mNpc->mRace, isFemale, *mResourceSystem->getVFS());
2019-03-20 00:50:05 +03:00
if (!isWerewolf && isVampire && !vampireHead.empty())
mHeadModel = vampireHead;
bool is1stPerson = mViewMode == VM_FirstPerson;
bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0;
std::string defaultSkeleton = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf);
defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS());
std::string smodel = defaultSkeleton;
2019-03-20 00:50:05 +03:00
if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty())
smodel = Misc::ResourceHelpers::correctActorModelPath(
Misc::ResourceHelpers::correctMeshPath(mNpc->mModel, mResourceSystem->getVFS()),
mResourceSystem->getVFS());
2015-04-23 23:30:06 +02:00
setObjectRoot(smodel, true, true, false);
updateParts();
if (!is1stPerson)
{
const std::string& base = Settings::Manager::getString("xbaseanim", "Models");
if (smodel != base && !isWerewolf)
addAnimSource(base, smodel);
2016-03-18 00:16:46 +01:00
if (smodel != defaultSkeleton && base != defaultSkeleton)
addAnimSource(defaultSkeleton, smodel);
addAnimSource(smodel, smodel);
2016-03-18 00:16:46 +01:00
if (!isWerewolf && mNpc->mRace.contains("argonian"))
addAnimSource("meshes\\xargonian_swimkna.nif", smodel);
}
else
{
const std::string& base = Settings::Manager::getString("xbaseanim1st", "Models");
if (smodel != base && !isWerewolf)
addAnimSource(base, smodel);
2022-09-22 21:26:05 +03:00
addAnimSource(smodel, smodel);
mObjectRoot->setNodeMask(Mask_FirstPerson);
mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView));
2022-09-22 21:26:05 +03:00
}
mWeaponAnimationTime->updateStartTime();
}
2015-05-31 01:07:43 +02:00
std::string NpcAnimation::getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const
2022-09-22 21:26:05 +03:00
{
2015-05-31 01:07:43 +02:00
std::string mesh = getShieldMesh(shield, !mNpc->isMale());
2012-01-17 15:10:53 +01:00
if (mesh.empty())
return std::string();
std::string holsteredName = mesh;
holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif");
if (mResourceSystem->getVFS()->exists(holsteredName))
2022-09-22 21:26:05 +03:00
{
osg::ref_ptr<osg::Node> shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName);
SceneUtil::FindByNameVisitor findVisitor("Bip01 Sheath");
shieldTemplate->accept(findVisitor);
osg::ref_ptr<osg::Node> sheathNode = findVisitor.mFoundNode;
if (!sheathNode)
return std::string();
2022-09-22 21:26:05 +03:00
}
return mesh;
}
void NpcAnimation::updateParts()
2022-09-22 21:26:05 +03:00
{
2015-04-17 01:23:37 +02:00
if (!mObjectRoot.get())
return;
NpcType curType = getNpcType();
2015-04-17 01:23:37 +02:00
if (curType != mNpcType)
2022-09-22 21:26:05 +03:00
{
mNpcType = curType;
2015-04-17 01:23:37 +02:00
rebuild();
return;
2022-09-22 21:26:05 +03:00
}
static const struct
2022-09-22 21:26:05 +03:00
{
2013-04-25 23:42:20 -07:00
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, 11 }, { 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 }
2022-09-22 21:26:05 +03:00
};
static const size_t slotlistsize = sizeof(slotlist) / sizeof(slotlist[0]);
2013-04-25 23:42:20 -07:00
bool wasArrowAttached = isArrowAttached();
mAmmunition.reset();
2019-12-14 21:30:46 +04:00
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
for (size_t i = 0; i < slotlistsize && mViewMode != VM_HeadOnly; i++)
2022-09-22 21:26:05 +03:00
{
MWWorld::ConstContainerStoreIterator store = inv.getSlot(slotlist[i].mSlot);
removePartGroup(slotlist[i].mSlot);
2013-04-25 23:42:20 -07:00
if (store == inv.end())
continue;
2012-01-17 15:10:53 +01:00
if (slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Helmet)
removeIndividualPart(ESM::PRT_Hair);
2013-04-25 23:42:20 -07:00
int prio = 1;
2013-11-20 00:07:26 +01:00
bool enchantedGlow = !store->getClass().getEnchantment(*store).empty();
2013-04-25 23:42:20 -07:00
osg::Vec4f glowColor = store->getClass().getEnchantmentColor(*store);
if (store->getType() == ESM::Clothing::sRecordId)
2022-09-22 21:26:05 +03:00
{
2013-04-25 23:42:20 -07:00
prio = ((slotlist[i].mBasePriority + 1) << 1) + 0;
const ESM::Clothing* clothes = store->get<ESM::Clothing>()->mBase;
2013-04-25 23:42:20 -07:00
addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts, enchantedGlow, &glowColor);
2022-09-22 21:26:05 +03:00
}
2013-04-25 23:42:20 -07:00
else if (store->getType() == ESM::Armor::sRecordId)
2022-09-22 21:26:05 +03:00
{
2013-04-25 23:42:20 -07:00
prio = ((slotlist[i].mBasePriority + 1) << 1) + 1;
const ESM::Armor* armor = store->get<ESM::Armor>()->mBase;
2013-04-25 23:42:20 -07:00
addPartGroup(slotlist[i].mSlot, prio, armor->mParts.mParts, enchantedGlow, &glowColor);
2022-09-22 21:26:05 +03:00
}
2012-04-13 23:25:15 -04:00
if (slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Robe)
2022-09-22 21:26:05 +03:00
{
ESM::PartReferenceType parts[] = { ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg,
2013-04-25 23:42:20 -07:00
ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, ESM::PRT_RForearm,
ESM::PRT_LForearm, ESM::PRT_Cuirass };
size_t parts_size = sizeof(parts) / sizeof(parts[0]);
2013-11-20 00:07:26 +01:00
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)
{
2013-11-20 00:07:26 +01:00
reserveIndividualPart(ESM::PRT_Groin, slotlist[i].mSlot, prio);
reserveIndividualPart(ESM::PRT_RLeg, slotlist[i].mSlot, prio);
reserveIndividualPart(ESM::PRT_LLeg, slotlist[i].mSlot, prio);
2022-09-22 21:26:05 +03:00
}
}
2012-01-17 15:10:53 +01:00
2013-04-25 23:42:20 -07:00
if (mViewMode != VM_FirstPerson)
{
if (mPartPriorities[ESM::PRT_Head] < 1 && !mHeadModel.empty())
addOrReplaceIndividualPart(ESM::PRT_Head, -1, 1, mHeadModel);
2020-03-19 21:30:22 +01:00
if (mPartPriorities[ESM::PRT_Hair] < 1 && mPartPriorities[ESM::PRT_Head] <= 1 && !mHairModel.empty())
2013-04-25 23:42:20 -07:00
addOrReplaceIndividualPart(ESM::PRT_Hair, -1, 1, mHairModel);
}
if (mViewMode == VM_HeadOnly)
return;
2013-08-04 18:58:10 -07:00
if (mPartPriorities[ESM::PRT_Shield] < 1)
{
MWWorld::ConstContainerStoreIterator store = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
MWWorld::ConstPtr part;
2013-08-04 18:58:10 -07:00
if (store != inv.end() && (part = *store).getType() == ESM::Light::sRecordId)
2022-09-22 21:26:05 +03:00
{
2013-08-04 18:58:10 -07:00
const ESM::Light* light = part.get<ESM::Light>()->mBase;
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1,
Misc::ResourceHelpers::correctMeshPath(light->mModel, vfs), false, nullptr, true);
if (mObjectParts[ESM::PRT_Shield])
addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), SceneUtil::LightCommon(*light));
2022-09-22 21:26:05 +03:00
}
2013-08-04 18:58:10 -07:00
}
showWeapons(mShowWeapons);
showCarriedLeft(mShowCarriedLeft);
bool isWerewolf = (getNpcType() == Type_Werewolf);
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
ESM::RefId race = (isWerewolf ? ESM::RefId::stringRefId("werewolf") : mNpc->mRace);
2016-02-09 00:26:22 +01:00
const std::vector<const ESM::BodyPart*>& parts
= getBodyParts(race, !mNpc->isMale(), mViewMode == VM_FirstPerson, isWerewolf);
for (int part = ESM::PRT_Neck; part < ESM::PRT_Count; ++part)
{
if (mPartPriorities[part] < 1)
{
const ESM::BodyPart* bodypart = parts[part];
2015-01-23 17:29:03 +01:00
if (bodypart)
2022-09-22 21:26:05 +03:00
{
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
addOrReplaceIndividualPart(static_cast<ESM::PartReferenceType>(part), -1, 1,
Misc::ResourceHelpers::correctMeshPath(bodypart->mModel, vfs));
2022-09-22 21:26:05 +03:00
}
}
}
if (wasArrowAttached)
attachArrow();
}
2022-08-21 18:53:38 +02:00
PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, std::string_view bonename,
std::string_view bonefilter, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight)
2022-09-22 21:26:05 +03:00
{
2021-11-02 18:01:22 +01:00
osg::ref_ptr<osg::Node> attached = attach(model, bonename, bonefilter, isLight);
2015-04-17 01:23:37 +02:00
if (enchantedGlow)
mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor);
2022-08-21 18:53:38 +02:00
return std::make_unique<PartHolder>(attached);
2022-09-22 21:26:05 +03:00
}
2022-08-21 18:53:38 +02:00
osg::Vec3f NpcAnimation::runAnimation(float timepassed)
2015-04-15 22:11:38 +02:00
{
2021-11-02 18:01:22 +01:00
osg::Vec3f ret = Animation::runAnimation(timepassed);
2015-04-19 01:57:52 +02:00
2022-08-21 18:53:38 +02:00
mHeadAnimationTime->update(timepassed);
2015-04-15 22:11:38 +02:00
2015-04-19 14:42:09 +02:00
if (mFirstPersonNeckController)
2022-09-22 21:26:05 +03:00
{
2015-04-19 14:42:09 +02:00
if (mAccurateAiming)
mAimingFactor = 1.f;
else
mAimingFactor = std::max(0.f, mAimingFactor - timepassed * 0.5f);
float rotateFactor = 0.75f + 0.25f * mAimingFactor;
mFirstPersonNeckController->setRotate(
osg::Quat(mPtr.getRefData().getPosition().rot[0] * rotateFactor, osg::Vec3f(-1, 0, 0)));
mFirstPersonNeckController->setOffset(mFirstPersonOffset);
2022-09-22 21:26:05 +03:00
}
WeaponAnimation::configureControllers(mPtr.getRefData().getPosition().rot[0] + getBodyPitchRadians());
return ret;
}
void NpcAnimation::removeIndividualPart(ESM::PartReferenceType type)
2022-09-22 21:26:05 +03:00
{
mPartPriorities[type] = 0;
mPartslots[type] = -1;
2012-04-12 20:40:11 -04:00
mObjectParts[type].reset();
if (mSounds[type] != nullptr && !mSoundsDisabled)
2022-09-22 21:26:05 +03:00
{
MWBase::Environment::get().getSoundManager()->stopSound(mSounds[type]);
mSounds[type] = nullptr;
2022-09-22 21:26:05 +03:00
}
}
void NpcAnimation::reserveIndividualPart(ESM::PartReferenceType type, int group, int priority)
{
if (priority > mPartPriorities[type])
2022-09-22 21:26:05 +03:00
{
removeIndividualPart(type);
mPartPriorities[type] = priority;
mPartslots[type] = group;
2022-09-22 21:26:05 +03:00
}
}
2012-04-12 20:40:11 -04:00
void NpcAnimation::removePartGroup(int group)
{
for (int i = 0; i < ESM::PRT_Count; i++)
2022-09-22 21:26:05 +03:00
{
if (mPartslots[i] == group)
removeIndividualPart((ESM::PartReferenceType)i);
2022-09-22 21:26:05 +03:00
}
2012-04-12 20:59:30 -04:00
}
2012-04-12 22:16:02 -04:00
2021-11-02 18:01:22 +01:00
bool NpcAnimation::isFemalePart(const ESM::BodyPart* bodypart)
{
2012-04-12 22:16:02 -04:00
return bodypart->mData.mFlags & ESM::BodyPart::BPF_Female;
2022-09-22 21:26:05 +03:00
}
2012-04-14 18:58:52 -04:00
bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority,
const std::string& mesh, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight)
{
2022-08-21 18:53:38 +02:00
if (priority <= mPartPriorities[type])
return false;
2022-09-22 21:26:05 +03:00
2022-08-21 18:53:38 +02:00
removeIndividualPart(type);
mPartslots[type] = group;
mPartPriorities[type] = priority;
2022-09-22 21:26:05 +03:00
try
{
std::string_view bonename = sPartList.at(type);
if (type == ESM::PRT_Weapon)
{
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
2022-08-21 18:53:38 +02:00
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weapon != inv.end() && weapon->getType() == ESM::Weapon::sRecordId)
2019-08-09 10:00:40 +04:00
{
int weaponType = weapon->get<ESM::Weapon>()->mBase->mData.mType;
const std::string& weaponBonename = MWMechanics::getWeaponType(weaponType)->mAttachBone;
2022-09-22 21:26:05 +03:00
2019-08-09 10:00:40 +04:00
if (weaponBonename != bonename)
2022-09-22 21:26:05 +03:00
{
2019-08-09 10:00:40 +04:00
const NodeMap& nodeMap = getNodeMap();
NodeMap::const_iterator found = nodeMap.find(weaponBonename);
2019-08-09 10:00:40 +04:00
if (found != nodeMap.end())
bonename = weaponBonename;
2022-09-22 21:26:05 +03:00
}
2019-08-09 10:00:40 +04:00
}
}
// PRT_Hair seems to be the only type that breaks consistency and uses a filter that's different from the
// attachment bone
2022-08-21 18:53:38 +02:00
const std::string_view bonefilter = (type == ESM::PRT_Hair) ? std::string_view{ "hair" } : bonename;
2021-11-02 18:01:22 +01:00
mObjectParts[type] = insertBoundedPart(mesh, bonename, bonefilter, enchantedGlow, glowColor, isLight);
}
catch (std::exception& e)
{
2018-08-14 23:05:43 +04:00
Log(Debug::Error) << "Error adding NPC part: " << e.what();
return false;
}
if (!mSoundsDisabled && group == MWWorld::InventoryStore::Slot_CarriedLeft)
{
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator csi = inv.getSlot(group);
if (csi != inv.end())
{
const auto soundId = csi->getClass().getSound(*csi);
if (!soundId.empty())
2022-09-22 21:26:05 +03:00
{
mSounds[type] = MWBase::Environment::get().getSoundManager()->playSound3D(
mPtr, soundId, 1.0f, 1.0f, MWSound::Type::Sfx, MWSound::PlayMode::Loop);
2022-09-22 21:26:05 +03:00
}
}
}
2015-04-19 14:42:09 +02:00
osg::Node* node = mObjectParts[type]->getNode();
if (node->getNumChildrenRequiringUpdateTraversal() > 0)
2015-04-19 14:42:09 +02:00
{
std::shared_ptr<SceneUtil::ControllerSource> src;
if (type == ESM::PRT_Head)
{
src = mHeadAnimationTime;
2022-09-22 21:26:05 +03:00
if (node->getUserDataContainer())
{
for (unsigned int i = 0; i < node->getUserDataContainer()->getNumUserObjects(); ++i)
2015-04-19 14:42:09 +02:00
{
osg::Object* obj = node->getUserDataContainer()->getUserObject(i);
if (SceneUtil::TextKeyMapHolder* keys = dynamic_cast<SceneUtil::TextKeyMapHolder*>(obj))
{
for (const auto& key : keys->mTextKeys)
2022-09-22 21:26:05 +03:00
{
2019-03-20 00:50:05 +03:00
if (Misc::StringUtils::ciEqual(key.second, "talk: start"))
mHeadAnimationTime->setTalkStart(key.first);
if (Misc::StringUtils::ciEqual(key.second, "talk: stop"))
mHeadAnimationTime->setTalkStop(key.first);
if (Misc::StringUtils::ciEqual(key.second, "blink: start"))
mHeadAnimationTime->setBlinkStart(key.first);
if (Misc::StringUtils::ciEqual(key.second, "blink: stop"))
mHeadAnimationTime->setBlinkStop(key.first);
2022-09-22 21:26:05 +03:00
}
break;
}
2015-04-19 14:42:09 +02:00
}
}
SceneUtil::ForceControllerSourcesVisitor assignVisitor(src);
node->accept(assignVisitor);
}
else
2022-09-22 21:26:05 +03:00
{
if (type == ESM::PRT_Weapon)
src = mWeaponAnimationTime;
2022-09-22 21:26:05 +03:00
else
src = std::make_shared<NullAnimationTime>();
SceneUtil::AssignControllerSourcesVisitor assignVisitor(src);
node->accept(assignVisitor);
2022-09-22 21:26:05 +03:00
}
}
return true;
}
2013-05-13 19:05:51 -07:00
2019-03-20 00:50:05 +03:00
void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::PartReference>& parts,
bool enchantedGlow, osg::Vec4f* glowColor)
{
2023-04-20 21:07:53 +02:00
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
2019-03-20 00:50:05 +03:00
const MWWorld::Store<ESM::BodyPart>& partStore = store.get<ESM::BodyPart>();
2022-09-22 21:26:05 +03:00
2019-03-20 00:50:05 +03:00
const char* ext = (mViewMode == VM_FirstPerson) ? ".1st" : "";
for (const ESM::PartReference& part : parts)
{
2019-03-20 00:50:05 +03:00
const ESM::BodyPart* bodypart = nullptr;
if (!mNpc->isMale() && !part.mFemale.empty())
{
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
bodypart = partStore.search(ESM::RefId::stringRefId(part.mFemale.getRefIdString() + ext));
if (!bodypart && mViewMode == VM_FirstPerson)
2022-09-22 21:26:05 +03:00
{
2019-03-20 00:50:05 +03:00
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))
2018-10-09 10:21:12 +04:00
bodypart = nullptr;
2022-09-22 21:26:05 +03:00
}
2015-01-23 17:29:03 +01:00
else if (!bodypart)
2019-03-20 00:50:05 +03:00
Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mFemale << "'";
}
2019-03-20 00:50:05 +03:00
if (!bodypart && !part.mMale.empty())
{
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
bodypart = partStore.search(ESM::RefId::stringRefId(part.mMale.getRefIdString() + ext));
if (!bodypart && mViewMode == VM_FirstPerson)
2022-09-22 21:26:05 +03:00
{
2019-03-20 00:50:05 +03:00
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))
2018-10-09 10:21:12 +04:00
bodypart = nullptr;
2022-09-22 21:26:05 +03:00
}
2015-01-23 17:29:03 +01:00
else if (!bodypart)
2019-03-20 00:50:05 +03:00
Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mMale << "'";
}
if (bodypart)
{
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
addOrReplaceIndividualPart(static_cast<ESM::PartReferenceType>(part.mPart), group, priority,
Misc::ResourceHelpers::correctMeshPath(bodypart->mModel, vfs), enchantedGlow, glowColor);
2022-09-22 21:26:05 +03:00
}
else
2019-03-20 00:50:05 +03:00
reserveIndividualPart((ESM::PartReferenceType)part.mPart, group, priority);
}
2012-04-14 18:58:52 -04:00
}
void NpcAnimation::addControllers()
{
Animation::addControllers();
2018-10-09 10:21:12 +04:00
mFirstPersonNeckController = nullptr;
2015-05-31 18:53:16 +02:00
WeaponAnimation::deleteControllers();
if (mViewMode == VM_FirstPerson)
{
NodeMap::iterator found = mNodeMap.find("bip01 neck");
2016-02-09 16:20:17 +01:00
if (found != mNodeMap.end())
2022-09-22 21:26:05 +03:00
{
2016-02-09 16:20:17 +01:00
osg::MatrixTransform* node = found->second.get();
mFirstPersonNeckController = new RotateController(mObjectRoot.get());
node->addUpdateCallback(mFirstPersonNeckController);
mActiveControllers.emplace_back(node, mFirstPersonNeckController);
2022-09-22 21:26:05 +03:00
}
}
2015-05-31 18:04:14 +02:00
else if (mViewMode == VM_Normal)
2022-09-22 21:26:05 +03:00
{
2015-05-31 18:53:16 +02:00
WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get());
}
}
void NpcAnimation::showWeapons(bool showWeapon)
{
mShowWeapons = showWeapon;
mAmmunition.reset();
if (showWeapon)
{
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
2013-11-20 00:07:26 +01:00
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weapon != inv.end())
{
osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon);
std::string mesh = weapon->getClass().getModel(*weapon);
addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh,
2013-11-20 00:07:26 +01:00
!weapon->getClass().getEnchantment(*weapon).empty(), &glowColor);
2022-09-22 21:26:05 +03:00
2014-03-12 11:30:44 +01:00
// Crossbows start out with a bolt attached
if (weapon->getType() == ESM::Weapon::sRecordId
2014-02-08 07:35:34 +01:00
&& weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
2022-09-22 21:26:05 +03:00
{
int ammotype = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow)->mAmmoType;
MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ammotype)
attachArrow();
2022-09-22 21:26:05 +03:00
}
}
}
else
{
removeIndividualPart(ESM::PRT_Weapon);
// If we remove/hide weapon from player, we should reset attack animation as well
if (mPtr == MWMechanics::getPlayer())
mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(false);
}
updateHolsteredWeapon(!mShowWeapons);
updateQuiver();
}
bool NpcAnimation::updateCarriedLeftVisible(const int weaptype) const
{
2023-06-27 23:41:06 +02:00
if (Settings::game().mShieldSheathing)
{
const MWWorld::Class& cls = mPtr.getClass();
MWMechanics::CreatureStats& stats = cls.getCreatureStats(mPtr);
if (stats.getDrawState() == MWMechanics::DrawState::Nothing)
{
SceneUtil::FindByNameVisitor findVisitor("Bip01 AttachShield");
mObjectRoot->accept(findVisitor);
if (findVisitor.mFoundNode || mViewMode == VM_FirstPerson)
2022-09-22 21:26:05 +03:00
{
const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr);
const MWWorld::ConstContainerStoreIterator shield
= inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId
&& !getSheathedShieldMesh(*shield).empty())
return false;
2022-09-22 21:26:05 +03:00
}
}
}
return !(MWMechanics::getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded);
}
void NpcAnimation::showCarriedLeft(bool show)
{
mShowCarriedLeft = show;
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if (show && iter != inv.end())
{
2021-11-02 18:01:22 +01:00
osg::Vec4f glowColor = iter->getClass().getEnchantmentColor(*iter);
std::string mesh = iter->getClass().getModel(*iter);
// For shields we must try to use the body part model
if (iter->getType() == ESM::Armor::sRecordId)
2022-09-22 21:26:05 +03:00
{
mesh = getShieldMesh(*iter, !mNpc->isMale());
2022-09-22 21:26:05 +03:00
}
if (mesh.empty()
|| addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, mesh,
2021-11-02 18:01:22 +01:00
!iter->getClass().getEnchantment(*iter).empty(), &glowColor,
iter->getType() == ESM::Light::sRecordId))
2022-09-22 21:26:05 +03:00
{
if (mesh.empty())
reserveIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1);
if (iter->getType() == ESM::Light::sRecordId && mObjectParts[ESM::PRT_Shield])
addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(),
SceneUtil::LightCommon(*iter->get<ESM::Light>()->mBase));
2022-09-22 21:26:05 +03:00
}
}
2022-09-22 21:26:05 +03:00
else
removeIndividualPart(ESM::PRT_Shield);
2022-09-22 21:26:05 +03:00
updateHolsteredShield(mShowCarriedLeft);
2022-09-22 21:26:05 +03:00
}
void NpcAnimation::attachArrow()
2022-09-22 21:26:05 +03:00
{
2014-03-12 11:30:44 +01:00
WeaponAnimation::attachArrow(mPtr);
2022-09-22 21:26:05 +03:00
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
2021-11-02 18:01:22 +01:00
if (ammo != inv.end() && !ammo->getClass().getEnchantment(*ammo).empty())
{
osg::Group* bone = getArrowBone();
if (bone != nullptr && bone->getNumChildren())
2015-04-19 15:03:08 +02:00
SceneUtil::addEnchantedGlow(
bone->getChild(0), mResourceSystem, ammo->getClass().getEnchantmentColor(*ammo));
}
updateQuiver();
}
2014-03-12 11:30:44 +01:00
void NpcAnimation::detachArrow()
{
WeaponAnimation::detachArrow(mPtr);
updateQuiver();
2022-09-22 21:26:05 +03:00
}
2019-08-07 09:45:10 +04:00
void NpcAnimation::releaseArrow(float attackStrength)
{
WeaponAnimation::releaseArrow(mPtr, attackStrength);
updateQuiver();
2019-08-07 09:45:10 +04:00
}
osg::Group* NpcAnimation::getArrowBone()
2022-09-22 21:26:05 +03:00
{
const PartHolder* const part = mObjectParts[ESM::PRT_Weapon].get();
2022-04-08 22:04:32 +02:00
if (part == nullptr)
2018-10-09 10:21:12 +04:00
return nullptr;
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId)
return nullptr;
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
int ammoType = MWMechanics::getWeaponType(type)->mAmmoType;
if (ammoType == ESM::Weapon::None)
return nullptr;
// Try to find and attachment bone in actor's skeleton, otherwise fall back to the ArrowBone in weapon's mesh
osg::Group* bone = getBoneByName(MWMechanics::getWeaponType(ammoType)->mAttachBone);
if (bone == nullptr)
2022-09-22 21:26:05 +03:00
{
SceneUtil::FindByNameVisitor findVisitor("ArrowBone");
part->getNode()->accept(findVisitor);
bone = findVisitor.mFoundNode;
2022-09-22 21:26:05 +03:00
}
return bone;
}
2015-05-31 01:07:43 +02:00
osg::Node* NpcAnimation::getWeaponNode()
2022-09-22 21:26:05 +03:00
{
2015-05-31 01:07:43 +02:00
const PartHolder* const part = mObjectParts[ESM::PRT_Weapon].get();
2022-04-08 22:04:32 +02:00
if (part == nullptr)
2015-05-31 01:07:43 +02:00
return nullptr;
return part->getNode();
}
Resource::ResourceSystem* NpcAnimation::getResourceSystem()
{
2015-04-19 14:42:09 +02:00
return mResourceSystem;
}
void NpcAnimation::enableHeadAnimation(bool enable)
{
mHeadAnimationTime->setEnabled(enable);
}
void NpcAnimation::setWeaponGroup(const std::string& group, bool relativeDuration)
{
mWeaponAnimationTime->setGroup(group, relativeDuration);
}
void NpcAnimation::equipmentChanged()
2022-09-22 21:26:05 +03:00
{
2023-06-27 23:41:06 +02:00
if (Settings::game().mShieldSheathing)
2022-09-22 21:26:05 +03:00
{
int weaptype = ESM::Weapon::None;
MWMechanics::getActiveWeapon(mPtr, &weaptype);
showCarriedLeft(updateCarriedLeftVisible(weaptype));
}
updateParts();
2022-09-22 21:26:05 +03:00
}
void NpcAnimation::setVampire(bool vampire)
{
if (mNpcType == Type_Werewolf) // we can't have werewolf vampires, can we
2022-09-22 21:26:05 +03:00
return;
if ((mNpcType == Type_Vampire) != vampire)
2022-09-22 21:26:05 +03:00
{
2015-08-21 21:12:39 +12:00
if (mPtr == MWMechanics::getPlayer())
MWBase::Environment::get().getWorld()->reattachPlayerCamera();
else
rebuild();
2022-09-22 21:26:05 +03:00
}
}
void NpcAnimation::setFirstPersonOffset(const osg::Vec3f& offset)
{
mFirstPersonOffset = offset;
}
2015-05-14 17:34:55 +02:00
void NpcAnimation::updatePtr(const MWWorld::Ptr& updated)
{
Animation::updatePtr(updated);
mHeadAnimationTime->updatePtr(updated);
}
2016-02-09 00:26:22 +01:00
// Remember body parts so we only have to search through the store once for each race/gender/viewmode combination
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
typedef std::map<std::pair<ESM::RefId, int>, std::vector<const ESM::BodyPart*>> RaceMapping;
2016-02-09 00:26:22 +01:00
static RaceMapping sRaceMapping;
const std::vector<const ESM::BodyPart*>& NpcAnimation::getBodyParts(
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
const ESM::RefId& race, bool female, bool firstPerson, bool werewolf)
2016-02-09 00:26:22 +01:00
{
static const int Flag_FirstPerson = 1 << 1;
static const int Flag_Female = 1 << 0;
2022-09-22 21:26:05 +03:00
2016-02-09 00:26:22 +01:00
int flags = (werewolf ? -1 : 0);
if (female)
2016-02-09 00:26:22 +01:00
flags |= Flag_Female;
if (firstPerson)
2016-02-09 00:26:22 +01:00
flags |= Flag_FirstPerson;
2022-09-22 21:26:05 +03:00
2016-02-09 00:26:22 +01:00
RaceMapping::iterator found = sRaceMapping.find(std::make_pair(race, flags));
if (found != sRaceMapping.end())
return found->second;
2019-03-20 00:50:05 +03:00
else
2016-02-09 00:26:22 +01:00
{
2019-03-20 00:50:05 +03:00
std::vector<const ESM::BodyPart*>& parts = sRaceMapping[std::make_pair(race, flags)];
2016-02-09 00:26:22 +01:00
2018-10-09 10:21:12 +04:00
typedef std::multimap<ESM::BodyPart::MeshPart, ESM::PartReferenceType> BodyPartMapType;
static const BodyPartMapType sBodyPartMap = { { ESM::BodyPart::MP_Neck, ESM::PRT_Neck },
{ ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass }, { ESM::BodyPart::MP_Groin, ESM::PRT_Groin },
2019-03-20 00:50:05 +03:00
{ ESM::BodyPart::MP_Hand, ESM::PRT_RHand }, { ESM::BodyPart::MP_Hand, ESM::PRT_LHand },
{ ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist }, { ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist },
{ ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm }, { ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm },
{ ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm }, { ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm },
{ ESM::BodyPart::MP_Foot, ESM::PRT_RFoot }, { ESM::BodyPart::MP_Foot, ESM::PRT_LFoot },
{ ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle }, { ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle },
{ ESM::BodyPart::MP_Knee, ESM::PRT_RKnee }, { ESM::BodyPart::MP_Knee, ESM::PRT_LKnee },
2018-10-09 10:21:12 +04:00
{ ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg }, { ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg },
2019-03-20 00:50:05 +03:00
{ ESM::BodyPart::MP_Tail, ESM::PRT_Tail } };
2016-02-09 00:26:22 +01:00
2019-03-20 00:50:05 +03:00
parts.resize(ESM::PRT_Count, nullptr);
2016-02-09 00:26:22 +01:00
if (werewolf)
return parts;
2019-03-20 00:50:05 +03:00
2023-04-20 21:07:53 +02:00
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
2016-02-09 00:26:22 +01:00
for (const ESM::BodyPart& bodypart : store.get<ESM::BodyPart>())
2022-09-22 21:26:05 +03:00
{
2016-02-09 00:26:22 +01:00
if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable)
continue;
if (bodypart.mData.mType != ESM::BodyPart::MT_Skin)
continue;
if (!(bodypart.mRace == race))
continue;
const bool partFirstPerson = ESM::isFirstPersonBodyPart(bodypart);
bool isHand = 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;
bool isSameGender = isFemalePart(&bodypart) == female;
/* A fallback for the arms if 1st person is missing:
1. Try to use 3d person skin for same gender
2. Try to use 1st person skin for male, if female == true
3. Try to use 3d person skin for male, if female == true
2022-09-22 21:26:05 +03:00
A fallback in another cases: allow to use male bodyparts, if female == true
*/
if (firstPerson && isHand && !partFirstPerson)
2016-02-09 00:26:22 +01:00
{
// 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)
2022-09-22 21:26:05 +03:00
{
// If we have no fallback bodypart now and bodypart is for same gender (1)
if (!parts[bIt->second] && isSameGender)
parts[bIt->second] = &bodypart;
// If we have fallback bodypart for other gender and found fallback for current gender (1)
else if (isSameGender && isFemalePart(parts[bIt->second]) != female)
parts[bIt->second] = &bodypart;
// If we have no fallback bodypart and searching for female bodyparts (3)
else if (!parts[bIt->second] && female)
parts[bIt->second] = &bodypart;
++bIt;
2016-02-09 00:26:22 +01:00
}
2016-02-09 00:26:22 +01:00
continue;
}
// Don't allow to use podyparts for a different view
if (partFirstPerson != firstPerson)
continue;
if (female && !isFemalePart(&bodypart))
2016-02-09 00:26:22 +01:00
{
// Allow male parts as fallback for females if female parts are missing
2016-02-09 00:26:22 +01:00
BodyPartMapType::const_iterator bIt
= sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart));
while (bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart)
2022-09-22 21:26:05 +03:00
{
// If we have no fallback bodypart now
2016-02-09 00:26:22 +01:00
if (!parts[bIt->second])
parts[bIt->second] = &bodypart;
// If we have 3d person fallback bodypart for hand and 1st person fallback found (2)
else if (isHand && !ESM::isFirstPersonBodyPart(*parts[bIt->second]) && partFirstPerson)
parts[bIt->second] = &bodypart;
2016-02-09 00:26:22 +01:00
++bIt;
}
2016-02-09 00:26:22 +01:00
continue;
}
// Don't allow to use podyparts for another gender
if (female != isFemalePart(&bodypart))
continue;
// Use properly found bodypart, replacing fallbacks
2016-02-09 00:26:22 +01:00
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] = &bodypart;
2016-02-09 00:26:22 +01:00
++bIt;
2022-09-22 21:26:05 +03:00
}
2016-02-09 00:26:22 +01:00
}
return parts;
}
}
void NpcAnimation::setAccurateAiming(bool enabled)
{
mAccurateAiming = enabled;
}
bool NpcAnimation::isArrowAttached() const
{
return mAmmunition != nullptr;
}
}