diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 5d7794e66..0a94e4ed4 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -836,6 +836,9 @@ bool CharacterController::updateWeaponState() mUpperBodyState == UpperCharState_FollowStartToFollowStop || mUpperBodyState == UpperCharState_CastingSpell) { + if (ammunition && mWeaponType == WeapType_Crossbow) + mAnimation->attachArrow(); + mUpperBodyState = UpperCharState_WeapEquiped; //don't allow to continue playing hit animation on UpperBody after actor had attacked during it if(mHitState == CharState_Hit) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 66153a7e0..ba662be88 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -695,6 +695,12 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co else mPtr.getClass().hit(mPtr); } + else if (evt.compare(off, len, "shoot attach") == 0) + attachArrow(); + else if (evt.compare(off, len, "shoot release") == 0) + releaseArrow(); + else if (evt.compare(off, len, "shoot follow attach") == 0) + attachArrow(); else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release") MWBase::Environment::get().getWorld()->castSpell(mPtr); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index ed7ed819f..c0cb18010 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -295,7 +295,8 @@ public: virtual void showWeapons(bool showWeapon); virtual void showCarriedLeft(bool show) {} - + virtual void attachArrow() {} + virtual void releaseArrow() {} void enableLights(bool enable); Ogre::AxisAlignedBox getWorldBounds(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d051bde52..bcbcbc737 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -696,6 +696,17 @@ void NpcAnimation::showWeapons(bool showWeapon) std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon); addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); + + if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) + { + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) + attachArrow(); + else + mAmmunition.setNull(); + } + else + mAmmunition.setNull(); } } else @@ -726,6 +737,52 @@ void NpcAnimation::showCarriedLeft(bool show) removeIndividualPart(ESM::PRT_Shield); } +void NpcAnimation::attachArrow() +{ + MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponSlot != inv.end() && weaponSlot->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + showWeapons(true); + else + { + NifOgre::ObjectScenePtr weapon = mObjectParts[ESM::PRT_Weapon]; + + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + std::string model = ammo->getClass().getModel(*ammo); + + mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", mInsert, model); + Ogre::Vector3 glowColor = getEnchantmentColor(*ammo); + setRenderProperties(mAmmunition, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, + !ammo->getClass().getEnchantment(*ammo).empty(), &glowColor); + + std::for_each(mAmmunition->mEntities.begin(), mAmmunition->mEntities.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); + std::for_each(mAmmunition->mParticles.begin(), mAmmunition->mParticles.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); + } +} + +void NpcAnimation::releaseArrow() +{ + // Thrown weapons get detached now + MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon != inv.end() && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + { + showWeapons(false); + inv.remove(*weapon, 1, mPtr); + } + else + { + // With bows and crossbows only the used arrow/bolt gets detached + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + inv.remove(*ammo, 1, mPtr); + mAmmunition.setNull(); + } +} + void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) { // During first auto equip, we don't play any sounds. diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 9baf975f1..725fde01d 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -135,6 +135,11 @@ public: virtual void showWeapons(bool showWeapon); virtual void showCarriedLeft(bool showa); + virtual void attachArrow(); + virtual void releaseArrow(); + + NifOgre::ObjectScenePtr mAmmunition; + void setViewMode(ViewMode viewMode); void updateParts(); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index e6dfe7a3e..e00276293 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -462,21 +462,23 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem( int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor) { - for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - if (mSlots[slot] == end()) - continue; + int retCount = ContainerStore::remove(item, count, actor); - if (*mSlots[slot] == item) + if (!item.getRefData().getCount()) + { + for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) { - // restacking is disabled cause it may break removal - unequipSlot(slot, actor, false); - break; + if (mSlots[slot] == end()) + continue; + + if (*mSlots[slot] == item) + { + unequipSlot(slot, actor); + break; + } } } - int retCount = ContainerStore::remove(item, count, actor); - // If an armor/clothing item is removed, try to find a replacement, // but not for the player nor werewolves. if ((actor.getRefData().getHandle() != "player") @@ -500,9 +502,9 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor return retCount; } -MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool restack) +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor) { - ContainerStoreIterator it = getSlot(slot); + ContainerStoreIterator it = mSlots[slot]; if (it != end()) { @@ -511,17 +513,15 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c // empty this slot mSlots[slot] = end(); - if (restack) { - // restack item previously in this slot - for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + // restack the previously equipped item with other (non-equipped) items + for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + { + if (stacks(*iter, *it)) { - if (stacks(*iter, *it)) - { - iter->getRefData().setCount(iter->getRefData().getCount() + it->getRefData().getCount()); - it->getRefData().setCount(0); - retval = iter; - break; - } + iter->getRefData().setCount(iter->getRefData().getCount() + it->getRefData().getCount()); + it->getRefData().setCount(0); + retval = iter; + break; } } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 868f3e517..714ba47da 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -168,12 +168,10 @@ namespace MWWorld /// /// @return the number of items actually removed - ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool restack = true); + ContainerStoreIterator unequipSlot(int slot, const Ptr& actor); ///< Unequip \a slot. /// /// @return an iterator to the item that was previously in the slot - /// (if \a restack is true, the item can be re-stacked so its count - /// may differ from when it was equipped). ContainerStoreIterator unequipItem(const Ptr& item, const Ptr& actor); ///< Unequip an item identified by its Ptr. An exception is thrown