1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-30 21:32:42 +00:00
OpenMW/apps/openmw/mwrender/actoranimation.cpp

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

593 lines
23 KiB
C++
Raw Normal View History

#include "actoranimation.hpp"
#include <utility>
#include <osg/Group>
#include <osg/Node>
#include <osg/Vec4f>
2022-09-08 21:08:59 +02:00
#include <components/esm3/loadbody.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/esm3/loadligh.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
2021-11-02 18:01:22 +01:00
#include <components/sceneutil/attach.hpp>
#include <components/sceneutil/lightcommon.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/lightutil.hpp>
#include <components/sceneutil/visitor.hpp>
#include <components/misc/resourcehelpers.hpp>
2023-06-27 23:41:06 +02:00
#include <components/settings/values.hpp>
#include <components/vfs/manager.hpp>
#include "../mwbase/environment.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/drawstate.hpp"
#include "../mwmechanics/weapontype.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/ptr.hpp"
#include "vismask.hpp"
namespace MWRender
{
ActorAnimation::ActorAnimation(
const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> parentNode, Resource::ResourceSystem* resourceSystem)
2023-07-29 12:58:27 +04:00
: Animation(ptr, std::move(parentNode), resourceSystem)
{
MWWorld::ContainerStore& store = mPtr.getClass().getContainerStore(mPtr);
2022-09-22 21:26:05 +03:00
for (MWWorld::ConstContainerStoreIterator iter = store.cbegin(MWWorld::ContainerStore::Type_Light);
iter != store.cend(); ++iter)
{
const ESM::Light* light = iter->get<ESM::Light>()->mBase;
if (!(light->mData.mFlags & ESM::Light::Carry))
2022-09-22 21:26:05 +03:00
{
addHiddenItemLight(*iter, light);
2022-09-22 21:26:05 +03:00
}
}
// Make sure we cleaned object from effects, just in cast if we re-use node
removeEffects();
}
ActorAnimation::~ActorAnimation()
{
removeFromSceneImpl();
}
PartHolderPtr ActorAnimation::attachMesh(
const std::string& model, std::string_view bonename, bool enchantedGlow, osg::Vec4f* glowColor)
2022-09-22 21:26:05 +03:00
{
osg::Group* parent = getBoneByName(bonename);
if (!parent)
return nullptr;
osg::ref_ptr<osg::Node> instance = mResourceSystem->getSceneManager()->getInstance(model, parent);
const NodeMap& nodeMap = getNodeMap();
NodeMap::const_iterator found = nodeMap.find(bonename);
if (found == nodeMap.end())
return {};
if (enchantedGlow)
2022-08-21 18:53:38 +02:00
mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor);
2022-08-21 18:53:38 +02:00
return std::make_unique<PartHolder>(instance);
2022-09-22 21:26:05 +03:00
}
2021-11-02 18:01:22 +01:00
2022-08-21 18:53:38 +02:00
osg::ref_ptr<osg::Node> ActorAnimation::attach(
const std::string& model, std::string_view bonename, std::string_view bonefilter, bool isLight)
2021-11-02 18:01:22 +01:00
{
osg::ref_ptr<const osg::Node> templateNode = mResourceSystem->getSceneManager()->getTemplate(model);
2022-09-22 21:26:05 +03:00
2021-11-02 18:01:22 +01:00
const NodeMap& nodeMap = getNodeMap();
auto found = nodeMap.find(bonename);
if (found == nodeMap.end())
2022-08-21 18:53:38 +02:00
throw std::runtime_error("Can't find attachment node " + std::string{ bonename });
2021-11-02 18:01:22 +01:00
if (isLight)
2022-09-22 21:26:05 +03:00
{
2021-11-02 18:01:22 +01:00
osg::Quat rotation(osg::DegreesToRadians(-90.f), osg::Vec3f(1, 0, 0));
return SceneUtil::attach(
templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager(), &rotation);
2022-09-22 21:26:05 +03:00
}
2021-11-02 18:01:22 +01:00
return SceneUtil::attach(
templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager());
}
std::string ActorAnimation::getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const
{
const ESM::Armor* armor = shield.get<ESM::Armor>()->mBase;
const std::vector<ESM::PartReference>& bodyparts = armor->mParts.mParts;
// Try to recover the body part model, use ground model as a fallback otherwise.
if (!bodyparts.empty())
{
2023-04-20 21:07:53 +02:00
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
const MWWorld::Store<ESM::BodyPart>& partStore = store.get<ESM::BodyPart>();
for (const auto& part : bodyparts)
{
if (part.mPart != ESM::PRT_Shield)
continue;
2022-09-22 21:26:05 +03:00
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* bodypartName = nullptr;
if (female && !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
bodypartName = &part.mFemale;
else if (!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
bodypartName = &part.mMale;
2022-09-22 21:26:05 +03:00
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
if (bodypartName && !bodypartName->empty())
2022-09-22 21:26:05 +03:00
{
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::BodyPart* bodypart = partStore.search(*bodypartName);
if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor)
return std::string();
if (!bodypart->mModel.empty())
return Misc::ResourceHelpers::correctMeshPath(bodypart->mModel);
2022-09-22 21:26:05 +03:00
}
}
}
return shield.getClass().getModel(shield);
}
std::string ActorAnimation::getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const
{
std::string mesh = getShieldMesh(shield, false);
if (mesh.empty())
return mesh;
std::string holsteredName = mesh;
holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif");
if (mResourceSystem->getVFS()->exists(holsteredName))
{
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();
}
return mesh;
}
bool ActorAnimation::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 (cls.hasInventoryStore(mPtr) && stats.getDrawState() == MWMechanics::DrawState::Nothing)
{
SceneUtil::FindByNameVisitor findVisitor("Bip01 AttachShield");
mObjectRoot->accept(findVisitor);
if (findVisitor.mFoundNode)
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
}
}
}
2022-09-22 21:26:05 +03:00
return !(MWMechanics::getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded);
}
void ActorAnimation::updateHolsteredShield(bool showCarriedLeft)
2022-09-22 21:26:05 +03:00
{
2023-06-27 23:41:06 +02:00
if (!Settings::game().mShieldSheathing)
return;
if (!mPtr.getClass().hasInventoryStore(mPtr))
return;
mHolsteredShield.reset();
if (showCarriedLeft)
return;
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if (shield == inv.end() || shield->getType() != ESM::Armor::sRecordId)
return;
// Can not show holdstered shields with two-handed weapons at all
const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weapon == inv.end())
return;
auto type = weapon->getType();
if (type == ESM::Weapon::sRecordId)
2022-09-22 21:26:05 +03:00
{
const MWWorld::LiveCellRef<ESM::Weapon>* ref = weapon->get<ESM::Weapon>();
ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType;
if (MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded)
return;
2022-09-22 21:26:05 +03:00
}
std::string mesh = getSheathedShieldMesh(*shield);
if (mesh.empty())
return;
std::string_view boneName = "Bip01 AttachShield";
osg::Vec4f glowColor = shield->getClass().getEnchantmentColor(*shield);
std::string holsteredName = mesh;
holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif");
bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty();
2022-08-21 18:53:38 +02:00
// If we have no dedicated sheath model, use basic shield model as fallback.
if (!mResourceSystem->getVFS()->exists(holsteredName))
mHolsteredShield = attachMesh(mesh, boneName, isEnchanted, &glowColor);
else
mHolsteredShield = attachMesh(holsteredName, boneName, isEnchanted, &glowColor);
if (!mHolsteredShield)
return;
SceneUtil::FindByNameVisitor findVisitor("Bip01 Sheath");
mHolsteredShield->getNode()->accept(findVisitor);
osg::Group* shieldNode = findVisitor.mFoundNode;
// If mesh author declared an empty sheath node, use transformation from this node, but use the common shield
// mesh. This approach allows to tweak shield position without need to store the whole shield mesh in the _sh
// file.
if (shieldNode && !shieldNode->getNumChildren())
2022-09-22 21:26:05 +03:00
{
osg::ref_ptr<osg::Node> fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, shieldNode);
if (isEnchanted)
SceneUtil::addEnchantedGlow(shieldNode, mResourceSystem, glowColor);
2022-09-22 21:26:05 +03:00
}
}
bool ActorAnimation::useShieldAnimations() const
{
2023-06-27 23:41:06 +02:00
if (!Settings::game().mShieldSheathing)
return false;
2022-09-22 21:26:05 +03:00
const MWWorld::Class& cls = mPtr.getClass();
if (!cls.hasInventoryStore(mPtr))
return false;
2022-09-22 21:26:05 +03:00
if (getTextKeyTime("shield: equip attach") < 0 || getTextKeyTime("shield: unequip detach") < 0)
return false;
2022-09-22 21:26:05 +03:00
const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr);
const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if (weapon != inv.end() && shield != inv.end() && shield->getType() == ESM::Armor::sRecordId
&& !getSheathedShieldMesh(*shield).empty())
2022-09-22 21:26:05 +03:00
{
auto type = weapon->getType();
if (type == ESM::Weapon::sRecordId)
2022-09-22 21:26:05 +03:00
{
const MWWorld::LiveCellRef<ESM::Weapon>* ref = weapon->get<ESM::Weapon>();
ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType;
return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded);
}
else if (type == ESM::Lockpick::sRecordId || type == ESM::Probe::sRecordId)
return true;
2022-09-22 21:26:05 +03:00
}
return false;
2022-09-22 21:26:05 +03:00
}
osg::Group* ActorAnimation::getBoneByName(std::string_view boneName) const
2022-09-22 21:26:05 +03:00
{
if (!mObjectRoot)
return nullptr;
SceneUtil::FindByNameVisitor findVisitor(boneName);
mObjectRoot->accept(findVisitor);
return findVisitor.mFoundNode;
2022-09-22 21:26:05 +03:00
}
std::string_view ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon)
{
if (weapon.isEmpty())
2022-08-21 18:53:38 +02:00
return {};
2022-09-22 21:26:05 +03:00
auto type = weapon.getClass().getType();
if (type == ESM::Weapon::sRecordId)
{
const MWWorld::LiveCellRef<ESM::Weapon>* ref = weapon.get<ESM::Weapon>();
int weaponType = ref->mBase->mData.mType;
return MWMechanics::getWeaponType(weaponType)->mSheathingBone;
}
2022-09-22 21:26:05 +03:00
return {};
}
void ActorAnimation::resetControllers(osg::Node* node)
2022-09-22 21:26:05 +03:00
{
if (node == nullptr)
return;
// This is used to avoid playing animations intended for equipped weapons on holstered weapons.
2022-08-21 18:53:38 +02:00
SceneUtil::ForceControllerSourcesVisitor removeVisitor(std::make_shared<NullAnimationTime>());
node->accept(removeVisitor);
2022-09-22 21:26:05 +03:00
}
2022-08-21 18:53:38 +02:00
void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
2022-09-22 21:26:05 +03:00
{
2023-06-27 23:41:06 +02:00
if (!Settings::game().mWeaponSheathing)
return;
if (!mPtr.getClass().hasInventoryStore(mPtr))
return;
2022-08-21 18:53:38 +02:00
mScabbard.reset();
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;
2022-08-21 18:53:38 +02:00
// Since throwing weapons stack themselves, do not show such weapon itself
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown)
2022-08-21 18:53:38 +02:00
showHolsteredWeapons = false;
std::string mesh = weapon->getClass().getModel(*weapon);
std::string scabbardName = mesh;
std::string_view boneName = getHolsteredWeaponBoneName(*weapon);
if (mesh.empty() || boneName.empty())
return;
// If the scabbard is not found, use the weapon mesh as fallback.
scabbardName = scabbardName.replace(scabbardName.size() - 4, 4, "_sh.nif");
bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty();
if (!mResourceSystem->getVFS()->exists(scabbardName))
{
if (showHolsteredWeapons)
2022-09-22 21:26:05 +03:00
{
osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon);
2019-08-27 09:55:02 +04:00
mScabbard = attachMesh(mesh, boneName, isEnchanted, &glowColor);
2019-03-10 11:03:51 +04:00
if (mScabbard)
resetControllers(mScabbard->getNode());
}
return;
}
2019-08-27 09:55:02 +04:00
mScabbard = attachMesh(scabbardName, boneName);
if (mScabbard)
resetControllers(mScabbard->getNode());
osg::Group* weaponNode = getBoneByName("Bip01 Weapon");
if (!weaponNode)
return;
// When we draw weapon, hide the Weapon node from sheath model.
// Otherwise add the enchanted glow to it.
if (!showHolsteredWeapons)
{
weaponNode->setNodeMask(0);
}
else
{
// If mesh author declared empty weapon node, use transformation from this node, but use the common weapon
// mesh. This approach allows to tweak weapon position without need to store the whole weapon mesh in the
// _sh file.
if (!weaponNode->getNumChildren())
2022-09-22 21:26:05 +03:00
{
osg::ref_ptr<osg::Node> fallbackNode
2019-02-17 11:42:22 +04:00
= mResourceSystem->getSceneManager()->getInstance(mesh, weaponNode);
resetControllers(fallbackNode);
2022-09-22 21:26:05 +03:00
}
if (isEnchanted)
2022-09-22 21:26:05 +03:00
{
osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon);
mGlowUpdater = SceneUtil::addEnchantedGlow(weaponNode, mResourceSystem, glowColor);
2022-09-22 21:26:05 +03:00
}
}
}
void ActorAnimation::updateQuiver()
{
2023-06-27 23:41:06 +02:00
if (!Settings::game().mWeaponSheathing)
return;
if (!mPtr.getClass().hasInventoryStore(mPtr))
return;
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId)
return;
std::string mesh = weapon->getClass().getModel(*weapon);
std::string_view boneName = getHolsteredWeaponBoneName(*weapon);
if (mesh.empty() || boneName.empty())
2022-09-22 21:26:05 +03:00
return;
osg::Group* ammoNode = getBoneByName("Bip01 Ammo");
2019-02-17 11:42:22 +04:00
if (!ammoNode)
return;
2019-02-17 11:42:22 +04:00
// Special case for throwing weapons - they do not use ammo, but they stack themselves
bool suitableAmmo = false;
MWWorld::ConstContainerStoreIterator ammo = weapon;
unsigned int ammoCount = 0;
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
2019-02-17 11:42:22 +04:00
const auto& weaponType = MWMechanics::getWeaponType(type);
if (weaponType->mWeaponClass == ESM::WeaponType::Thrown)
2022-09-22 21:26:05 +03:00
{
2023-12-31 17:12:46 +00:00
ammoCount = ammo->getCellRef().getCount();
osg::Group* throwingWeaponNode = getBoneByName(weaponType->mAttachBone);
2019-02-17 11:42:22 +04:00
if (throwingWeaponNode && throwingWeaponNode->getNumChildren())
ammoCount--;
2019-02-17 11:42:22 +04:00
suitableAmmo = true;
2022-09-22 21:26:05 +03:00
}
2019-02-17 11:42:22 +04:00
else
2022-09-22 21:26:05 +03:00
{
2019-02-17 11:42:22 +04:00
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if (ammo == inv.end())
return;
2023-12-31 17:12:46 +00:00
ammoCount = ammo->getCellRef().getCount();
2019-02-17 11:42:22 +04:00
bool arrowAttached = isArrowAttached();
if (arrowAttached)
ammoCount--;
suitableAmmo = ammo->get<ESM::Weapon>()->mBase->mData.mType == weaponType->mAmmoType;
2022-09-22 21:26:05 +03:00
}
if (!suitableAmmo)
2022-09-22 21:26:05 +03:00
return;
// We should not show more ammo than equipped and more than quiver mesh has
ammoCount = std::min(ammoCount, ammoNode->getNumChildren());
2022-09-22 21:26:05 +03:00
2019-02-17 11:42:22 +04:00
// Remove existing ammo nodes
for (unsigned int i = 0; i < ammoNode->getNumChildren(); ++i)
2022-09-22 21:26:05 +03:00
{
2019-02-17 11:42:22 +04:00
osg::ref_ptr<osg::Group> arrowNode = ammoNode->getChild(i)->asGroup();
if (!arrowNode->getNumChildren())
2019-02-17 11:42:22 +04:00
continue;
2022-09-22 21:26:05 +03:00
2019-02-17 11:42:22 +04:00
osg::ref_ptr<osg::Node> arrowChildNode = arrowNode->getChild(0);
arrowNode->removeChild(arrowChildNode);
2022-09-22 21:26:05 +03:00
}
2019-02-17 11:42:22 +04:00
// Add new ones
osg::Vec4f glowColor = ammo->getClass().getEnchantmentColor(*ammo);
2019-02-17 11:42:22 +04:00
std::string model = ammo->getClass().getModel(*ammo);
for (unsigned int i = 0; i < ammoCount; ++i)
{
osg::ref_ptr<osg::Group> arrowNode = ammoNode->getChild(i)->asGroup();
osg::ref_ptr<osg::Node> arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode);
if (!ammo->getClass().getEnchantment(*ammo).empty())
2023-07-29 12:58:27 +04:00
mGlowUpdater = SceneUtil::addEnchantedGlow(std::move(arrow), mResourceSystem, glowColor);
2022-09-22 21:26:05 +03:00
}
}
void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/)
{
if (item.getType() == ESM::Light::sRecordId)
{
const ESM::Light* light = item.get<ESM::Light>()->mBase;
if (!(light->mData.mFlags & ESM::Light::Carry))
2022-09-22 21:26:05 +03:00
{
addHiddenItemLight(item, light);
2022-09-22 21:26:05 +03:00
}
}
if (!mPtr.getClass().hasInventoryStore(mPtr))
return;
// If the count of equipped ammo or throwing weapon was changed, we should update quiver
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId)
return;
MWWorld::ConstContainerStoreIterator ammo = inv.end();
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown)
ammo = weapon;
else
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if (ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId())
updateQuiver();
}
void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/)
{
if (item.getType() == ESM::Light::sRecordId)
{
ItemLightMap::iterator iter = mItemLights.find(item);
if (iter != mItemLights.end())
{
2023-12-31 17:12:46 +00:00
if (!item.getCellRef().getCount())
2022-09-22 21:26:05 +03:00
{
removeHiddenItemLight(item);
2022-09-22 21:26:05 +03:00
}
}
}
if (!mPtr.getClass().hasInventoryStore(mPtr))
return;
// If the count of equipped ammo or throwing weapon was changed, we should update quiver
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId)
2022-09-22 21:26:05 +03:00
return;
MWWorld::ConstContainerStoreIterator ammo = inv.end();
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown)
ammo = weapon;
else
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if (ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId())
updateQuiver();
2022-09-22 21:26:05 +03:00
}
void ActorAnimation::addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight)
2022-09-22 21:26:05 +03:00
{
if (mItemLights.find(item) != mItemLights.end())
return;
bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior();
osg::Vec4f ambient(1, 1, 1, 1);
osg::ref_ptr<SceneUtil::LightSource> lightSource
= SceneUtil::createLightSource(SceneUtil::LightCommon(*esmLight), Mask_Lighting, exterior, ambient);
mInsert->addChild(lightSource);
if (mLightListCallback && mPtr == MWMechanics::getPlayer())
mLightListCallback->getIgnoredLightSources().insert(lightSource.get());
mItemLights.insert(std::make_pair(item, lightSource));
2022-09-22 21:26:05 +03:00
}
void ActorAnimation::removeHiddenItemLight(const MWWorld::ConstPtr& item)
{
ItemLightMap::iterator iter = mItemLights.find(item);
if (iter == mItemLights.end())
return;
if (mLightListCallback && mPtr == MWMechanics::getPlayer())
2022-09-22 21:26:05 +03:00
{
std::set<SceneUtil::LightSource*>::iterator ignoredIter
= mLightListCallback->getIgnoredLightSources().find(iter->second.get());
if (ignoredIter != mLightListCallback->getIgnoredLightSources().end())
mLightListCallback->getIgnoredLightSources().erase(ignoredIter);
}
2021-11-02 18:01:22 +01:00
mInsert->removeChild(iter->second);
mItemLights.erase(iter);
2022-09-22 21:26:05 +03:00
}
void ActorAnimation::removeFromScene()
{
removeFromSceneImpl();
Animation::removeFromScene();
}
void ActorAnimation::removeFromSceneImpl()
{
for (const auto& [k, v] : mItemLights)
mInsert->removeChild(v);
}
}