diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 774bf3aea..5c893845d 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -83,7 +83,7 @@ add_openmw_dir (mwmechanics drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe aicast aiescort aiface aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning - character actors objects aistate coordinateconverter trading weaponpriority spellpriority + character actors objects aistate coordinateconverter trading weaponpriority spellpriority weapontype ) add_openmw_dir (mwstate diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 7ab1d49fe..75a55a077 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -20,6 +20,7 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/weapontype.hpp" #include "../mwgui/tooltips.hpp" @@ -332,21 +333,13 @@ namespace MWClass if(*slot == MWWorld::InventoryStore::Slot_CarriedLeft) { MWWorld::ConstContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - - if(weapon == invStore.end()) - return std::make_pair(1,""); - - if(weapon->getTypeName() == typeid(ESM::Weapon).name() && - (weapon->get()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand || - weapon->get()->mBase->mData.mType == ESM::Weapon::BluntTwoClose || - weapon->get()->mBase->mData.mType == ESM::Weapon::BluntTwoWide || - weapon->get()->mBase->mData.mType == ESM::Weapon::SpearTwoWide || - weapon->get()->mBase->mData.mType == ESM::Weapon::AxeTwoHand || - weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanBow || - weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)) + if(weapon != invStore.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()) { - return std::make_pair(3,""); + const MWWorld::LiveCellRef *ref = weapon->get(); + if (MWMechanics::getWeaponType(ref->mBase->mData.mType)->mFlags & ESM::WeaponType::TwoHanded) + return std::make_pair(3,""); } + return std::make_pair(1,""); } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 87397efa2..92845aa88 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -26,7 +26,7 @@ #include "../mwmechanics/combat.hpp" #include "../mwmechanics/autocalcspell.hpp" #include "../mwmechanics/difficultyscaling.hpp" -#include "../mwmechanics/character.hpp" +#include "../mwmechanics/weapontype.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/ptr.hpp" @@ -1228,9 +1228,9 @@ namespace MWClass if (getNpcStats(ptr).isWerewolf() && getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)) { - MWMechanics::WeaponType weaponType = MWMechanics::WeapType_None; - MWMechanics::getActiveWeapon(getCreatureStats(ptr), getInventoryStore(ptr), &weaponType); - if (weaponType == MWMechanics::WeapType_None) + int weaponType = ESM::Weapon::None; + MWMechanics::getActiveWeapon(ptr, &weaponType); + if (weaponType == ESM::Weapon::None) return std::string(); } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index dd6d63166..267ba0c75 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -17,6 +17,8 @@ #include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" +#include "../mwmechanics/weapontype.hpp" + #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" @@ -64,8 +66,9 @@ namespace MWClass bool Weapon::hasItemHealth (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); + int type = ref->mBase->mData.mType; - return (ref->mBase->mData.mType < ESM::Weapon::MarksmanThrown); // thrown weapons and arrows/bolts don't have health, only quantity + return MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::HasHealth; } int Weapon::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const @@ -86,16 +89,17 @@ namespace MWClass std::pair, bool> Weapon::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); + ESM::WeaponType::Class weapClass = MWMechanics::getWeaponType(ref->mBase->mData.mType)->mWeaponClass; std::vector slots_; bool stack = false; - if (ref->mBase->mData.mType==ESM::Weapon::Arrow || ref->mBase->mData.mType==ESM::Weapon::Bolt) + if (weapClass == ESM::WeaponType::Ammo) { slots_.push_back (int (MWWorld::InventoryStore::Slot_Ammunition)); stack = true; } - else if (ref->mBase->mData.mType==ESM::Weapon::MarksmanThrown) + else if (weapClass == ESM::WeaponType::Thrown) { slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); stack = true; @@ -109,30 +113,9 @@ namespace MWClass int Weapon::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); + int type = ref->mBase->mData.mType; - const int size = 12; - - static const int sMapping[size][2] = - { - { ESM::Weapon::ShortBladeOneHand, ESM::Skill::ShortBlade }, - { ESM::Weapon::LongBladeOneHand, ESM::Skill::LongBlade }, - { ESM::Weapon::LongBladeTwoHand, ESM::Skill::LongBlade }, - { ESM::Weapon::BluntOneHand, ESM::Skill::BluntWeapon }, - { ESM::Weapon::BluntTwoClose, ESM::Skill::BluntWeapon }, - { ESM::Weapon::BluntTwoWide, ESM::Skill::BluntWeapon }, - { ESM::Weapon::SpearTwoWide, ESM::Skill::Spear }, - { ESM::Weapon::AxeOneHand, ESM::Skill::Axe }, - { ESM::Weapon::AxeTwoHand, ESM::Skill::Axe }, - { ESM::Weapon::MarksmanBow, ESM::Skill::Marksman }, - { ESM::Weapon::MarksmanCrossbow, ESM::Skill::Marksman }, - { ESM::Weapon::MarksmanThrown, ESM::Skill::Marksman } - }; - - for (int i=0; imBase->mData.mType) - return sMapping[i][1]; - - return -1; + return MWMechanics::getWeaponType(type)->mSkill; } int Weapon::getValue (const MWWorld::ConstPtr& ptr) const @@ -152,89 +135,17 @@ namespace MWClass std::string Weapon::getUpSoundId (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); - int type = ref->mBase->mData.mType; - // Ammo - if (type == 12 || type == 13) - { - return std::string("Item Ammo Up"); - } - // Bow - if (type == 9) - { - return std::string("Item Weapon Bow Up"); - } - // Crossbow - if (type == 10) - { - return std::string("Item Weapon Crossbow Up"); - } - // Longblades, One hand and Two - if (type == 1 || type == 2) - { - return std::string("Item Weapon Longblade Up"); - } - // Shortblade - if (type == 0) - { - return std::string("Item Weapon Shortblade Up"); - } - // Spear - if (type == 6) - { - return std::string("Item Weapon Spear Up"); - } - // Blunts, Axes and Thrown weapons - if (type == 3 || type == 4 || type == 5 || type == 7 || type == 8 || type == 11) - { - return std::string("Item Weapon Blunt Up"); - } - - return std::string("Item Misc Up"); + std::string soundId = MWMechanics::getWeaponType(type)->mSoundId; + return soundId + " Up"; } std::string Weapon::getDownSoundId (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); - int type = ref->mBase->mData.mType; - // Ammo - if (type == 12 || type == 13) - { - return std::string("Item Ammo Down"); - } - // Bow - if (type == 9) - { - return std::string("Item Weapon Bow Down"); - } - // Crossbow - if (type == 10) - { - return std::string("Item Weapon Crossbow Down"); - } - // Longblades, One hand and Two - if (type == 1 || type == 2) - { - return std::string("Item Weapon Longblade Down"); - } - // Shortblade - if (type == 0) - { - return std::string("Item Weapon Shortblade Down"); - } - // Spear - if (type == 6) - { - return std::string("Item Weapon Spear Down"); - } - // Blunts, Axes and Thrown weapons - if (type == 3 || type == 4 || type == 5 || type == 7 || type == 8 || type == 11) - { - return std::string("Item Weapon Blunt Down"); - } - - return std::string("Item Misc Down"); + std::string soundId = MWMechanics::getWeaponType(type)->mSoundId; + return soundId + " Down"; } std::string Weapon::getInventoryIcon (const MWWorld::ConstPtr& ptr) const @@ -254,6 +165,7 @@ namespace MWClass MWGui::ToolTipInfo Weapon::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); + const ESM::WeaponType* weaponType = MWMechanics::getWeaponType(ref->mBase->mData.mType); MWGui::ToolTipInfo info; info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); @@ -264,37 +176,26 @@ namespace MWClass std::string text; // weapon type & damage - if ((ref->mBase->mData.mType < ESM::Weapon::Arrow || Settings::Manager::getBool("show projectile damage", "Game")) && ref->mBase->mData.mType <= ESM::Weapon::Bolt) + if (weaponType->mWeaponClass != ESM::WeaponType::Ammo || Settings::Manager::getBool("show projectile damage", "Game")) { text += "\n#{sType} "; - static std::map > mapping; - if (mapping.empty()) + int skill = MWMechanics::getWeaponType(ref->mBase->mData.mType)->mSkill; + const std::string type = ESM::Skill::sSkillNameIds[skill]; + std::string oneOrTwoHanded; + if (weaponType->mWeaponClass == ESM::WeaponType::Melee) { - mapping[ESM::Weapon::ShortBladeOneHand] = std::make_pair("sSkillShortblade", "sOneHanded"); - mapping[ESM::Weapon::LongBladeOneHand] = std::make_pair("sSkillLongblade", "sOneHanded"); - mapping[ESM::Weapon::LongBladeTwoHand] = std::make_pair("sSkillLongblade", "sTwoHanded"); - mapping[ESM::Weapon::BluntOneHand] = std::make_pair("sSkillBluntweapon", "sOneHanded"); - mapping[ESM::Weapon::BluntTwoClose] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); - mapping[ESM::Weapon::BluntTwoWide] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); - mapping[ESM::Weapon::SpearTwoWide] = std::make_pair("sSkillSpear", "sTwoHanded"); - mapping[ESM::Weapon::AxeOneHand] = std::make_pair("sSkillAxe", "sOneHanded"); - mapping[ESM::Weapon::AxeTwoHand] = std::make_pair("sSkillAxe", "sTwoHanded"); - mapping[ESM::Weapon::MarksmanBow] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::MarksmanCrossbow] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::MarksmanThrown] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::Arrow] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::Bolt] = std::make_pair("sSkillMarksman", ""); + if (weaponType->mFlags & ESM::WeaponType::TwoHanded) + oneOrTwoHanded = "sTwoHanded"; + else + oneOrTwoHanded = "sOneHanded"; } - const std::string type = mapping[ref->mBase->mData.mType].first; - const std::string oneOrTwoHanded = mapping[ref->mBase->mData.mType].second; - text += store.get().find(type)->mValue.getString() + ((oneOrTwoHanded != "") ? ", " + store.get().find(oneOrTwoHanded)->mValue.getString() : ""); // weapon damage - if (ref->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + if (weaponType->mWeaponClass == ESM::WeaponType::Thrown) { // Thrown weapons have 2x real damage applied // as they're both the weapon and the ammo @@ -302,14 +203,7 @@ namespace MWClass + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0] * 2)) + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1] * 2)); } - else if (ref->mBase->mData.mType >= ESM::Weapon::MarksmanBow) - { - // marksman - text += "\n#{sAttack}: " - + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0])) - + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1])); - } - else + else if (weaponType->mWeaponClass == ESM::WeaponType::Melee) { // Chop text += "\n#{sChop}: " @@ -324,6 +218,13 @@ namespace MWClass + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mThrust[0])) + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mThrust[1])); } + else + { + // marksman + text += "\n#{sAttack}: " + + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0])) + + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1])); + } } if (hasItemHealth(ptr)) @@ -335,7 +236,7 @@ namespace MWClass const bool verbose = Settings::Manager::getBool("show melee info", "Game"); // add reach for melee weapon - if (ref->mBase->mData.mType < ESM::Weapon::MarksmanBow && verbose) + if (weaponType->mWeaponClass == ESM::WeaponType::Melee && verbose) { // display value in feet const float combatDistance = store.get().find("fCombatDistance")->mValue.getFloat() * ref->mBase->mData.mReach; @@ -344,7 +245,7 @@ namespace MWClass } // add attack speed for any weapon excepts arrows and bolts - if (ref->mBase->mData.mType < ESM::Weapon::Arrow && verbose) + if (weaponType->mWeaponClass != ESM::WeaponType::Ammo && verbose) { text += MWGui::ToolTips::getPercentString(ref->mBase->mData.mSpeed, "#{sAttributeSpeed}"); } @@ -403,13 +304,8 @@ namespace MWClass if (slots_.first.empty()) return std::make_pair (0, ""); - if(ptr.get()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand || - ptr.get()->mBase->mData.mType == ESM::Weapon::BluntTwoClose || - ptr.get()->mBase->mData.mType == ESM::Weapon::BluntTwoWide || - ptr.get()->mBase->mData.mType == ESM::Weapon::SpearTwoWide || - ptr.get()->mBase->mData.mType == ESM::Weapon::AxeTwoHand || - ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanBow || - ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) + int type = ptr.get()->mBase->mData.mType; + if(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded) { return std::make_pair (2, ""); } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 78e65ce15..ee1b9cecd 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -445,10 +445,9 @@ namespace MWMechanics if (targetClass.hasInventoryStore(target)) { - MWMechanics::WeaponType weapType = WeapType_None; - MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(targetClass.getCreatureStats(target), targetClass.getInventoryStore(target), &weapType); - if (weapType != WeapType_PickProbe && weapType != WeapType_Spell && weapType != WeapType_None && weapType != WeapType_HandToHand) + int weapType = ESM::Weapon::None; + MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(target, &weapType); + if (weapType > ESM::Weapon::None) targetWeapon = *weaponSlot; } @@ -645,7 +644,7 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); // get projectile speed (depending on weapon type) - if (weapType == ESM::Weapon::MarksmanThrown) + if (MWMechanics::getWeaponType(weapType)->mWeaponClass == ESM::WeaponType::Thrown) { static float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->mValue.getFloat(); static float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->mValue.getFloat(); diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 167e13128..9f698b630 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -18,6 +18,7 @@ #include "combat.hpp" #include "weaponpriority.hpp" #include "spellpriority.hpp" +#include "weapontype.hpp" namespace MWMechanics { @@ -125,8 +126,7 @@ namespace MWMechanics } const ESM::Weapon* weapon = mWeapon.get()->mBase; - - if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) + if (MWMechanics::getWeaponType(weapon->mData.mType)->mWeaponClass != ESM::WeaponType::Melee) { isRanged = true; return fProjectileMaxSpeed; @@ -194,11 +194,12 @@ namespace MWMechanics if (rating > bestActionRating) { const ESM::Weapon* weapon = it->get()->mBase; + int ammotype = getWeaponType(weapon->mData.mType)->mAmmoType; MWWorld::Ptr ammo; - if (weapon->mData.mType == ESM::Weapon::MarksmanBow) + if (ammotype == ESM::Weapon::Arrow) ammo = bestArrow; - else if (weapon->mData.mType == ESM::Weapon::MarksmanCrossbow) + else if (ammotype == ESM::Weapon::Bolt) ammo = bestBolt; bestActionRating = rating; @@ -367,7 +368,7 @@ namespace MWMechanics else if (!activeWeapon.isEmpty()) { const ESM::Weapon* esmWeap = activeWeapon.get()->mBase; - if (esmWeap->mData.mType >= ESM::Weapon::MarksmanBow) + if (MWMechanics::getWeaponType(esmWeap->mData.mType)->mWeaponClass != ESM::WeaponType::Melee) { static const float fTargetSpellMaxSpeed = gmst.find("fProjectileMaxSpeed")->mValue.getFloat(); dist = fTargetSpellMaxSpeed; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index e7c9b2d38..9f3a88193 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -198,33 +198,6 @@ public: }; -static const struct WeaponInfo { - WeaponType type; - const char shortgroup[16]; - const char longgroup[16]; -} sWeaponTypeList[] = { - { WeapType_HandToHand, "hh", "handtohand" }, - { WeapType_OneHand, "1h", "weapononehand" }, - { WeapType_TwoHand, "2c", "weapontwohand" }, - { WeapType_TwoWide, "2w", "weapontwowide" }, - { WeapType_BowAndArrow, "1h", "bowandarrow" }, - { WeapType_Crossbow, "crossbow", "crossbow" }, - { WeapType_Thrown, "1h", "throwweapon" }, - { WeapType_PickProbe, "1h", "pickprobe" }, - { WeapType_Spell, "spell", "spellcast" }, -}; -static const WeaponInfo *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])]; - -class FindWeaponType { - WeaponType type; - -public: - FindWeaponType(WeaponType _type) : type(_type) { } - - bool operator()(const WeaponInfo &weap) const - { return weap.type == type; } -}; - std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const { int numAnims=0; @@ -315,7 +288,7 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) { mAnimation->disable(mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; - if (mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell) + if (mWeaponType > ESM::Weapon::None) mAnimation->showWeapons(true); } else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped) @@ -347,7 +320,7 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) idle = CharState_None; } -void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, bool force) +void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force) { if (!force && jump == mJumpState && idle == CharState_None) return; @@ -357,22 +330,17 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState if (jump != JumpState_None) { jumpAnimName = "jump"; - if(weap != sWeaponTypeListEnd) + if(!weapShortGroup.empty()) { - jumpAnimName += weap->shortgroup; + jumpAnimName += weapShortGroup; if(!mAnimation->hasAnimation(jumpAnimName)) { - jumpmask = MWRender::Animation::BlendMask_LowerBody; - jumpAnimName = "jump"; + jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask); - // Since we apply movement only for lower body, do not reset idle animations. + // If we apply jump only for lower body, do not reset idle animations. // For upper body there will be idle animation. - if (idle == CharState_None) + if (jumpmask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None) idle = CharState_Idle; - - // For crossbow animations use 1h ones as fallback - if (mWeaponType == WeapType_Crossbow) - jumpAnimName += "1h"; } } } @@ -446,7 +414,65 @@ void CharacterController::onClose() } } -void CharacterController::refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, CharacterState& idle, bool force) +std::string CharacterController::getWeaponAnimation(int weaponType) const +{ + std::string weaponGroup = getWeaponType(weaponType)->mLongGroup; + bool isRealWeapon = weaponType != ESM::Weapon::HandToHand && weaponType != ESM::Weapon::Spell && weaponType != ESM::Weapon::None; + if (isRealWeapon && !mAnimation->hasAnimation(weaponGroup)) + { + static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup; + static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup; + + const ESM::WeaponType* weapInfo = getWeaponType(weaponType); + + // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones + if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) + weaponGroup = twoHandFallback; + else if (isRealWeapon) + weaponGroup = oneHandFallback; + } + + return weaponGroup; +} + +std::string CharacterController::fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask) +{ + bool isRealWeapon = mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None; + if (!isRealWeapon) + { + if (blendMask != nullptr) + *blendMask = MWRender::Animation::BlendMask_LowerBody; + + return baseGroupName; + } + + static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mShortGroup; + static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mShortGroup; + + std::string groupName = baseGroupName; + const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType); + + // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones + if (isRealWeapon && weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) + groupName += twoHandFallback; + else if (isRealWeapon) + groupName += oneHandFallback; + + // Special case for crossbows - we shouls apply 1h animations a fallback only for lower body + if (mWeaponType == ESM::Weapon::MarksmanCrossbow && blendMask != nullptr) + *blendMask = MWRender::Animation::BlendMask_LowerBody; + + if (!mAnimation->hasAnimation(groupName)) + { + groupName = baseGroupName; + if (blendMask != nullptr) + *blendMask = MWRender::Animation::BlendMask_LowerBody; + } + + return groupName; +} + +void CharacterController::refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force) { if (movement == mMovementState && idle == mIdleState && !force) return; @@ -460,15 +486,15 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character if(movestate != sMovementListEnd) { movementAnimName = movestate->groupname; - if(weap != sWeaponTypeListEnd) + if(!weapShortGroup.empty()) { std::string::size_type swimpos = movementAnimName.find("swim"); if (swimpos == std::string::npos) { - if (mWeaponType == WeapType_Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case - movementAnimName = weap->shortgroup + movementAnimName; + if (mWeaponType == ESM::Weapon::Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case + movementAnimName = weapShortGroup + movementAnimName; else - movementAnimName += weap->shortgroup; + movementAnimName += weapShortGroup; } if(!mAnimation->hasAnimation(movementAnimName)) @@ -476,15 +502,12 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character movementAnimName = movestate->groupname; if (swimpos == std::string::npos) { - movemask = MWRender::Animation::BlendMask_LowerBody; - // Since we apply movement only for lower body, do not reset idle animations. + movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); + + // If we apply movement only for lower body, do not reset idle animations. // For upper body there will be idle animation. - if (idle == CharState_None) + if (movemask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None) idle = CharState_Idle; - - // For crossbow animations use 1h ones as fallback - if (mWeaponType == WeapType_Crossbow) - movementAnimName += "1h"; } else if (idle == CharState_None) { @@ -520,18 +543,14 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character } else { - // For crossbow animations use 1h ones as fallback - if (mWeaponType == WeapType_Crossbow) - movementAnimName += "1h"; - movementAnimName.erase(swimpos, 4); - if (weap != sWeaponTypeListEnd) + if (!weapShortGroup.empty()) { - std::string weapMovementAnimName = movementAnimName + weap->shortgroup; + std::string weapMovementAnimName = movementAnimName + weapShortGroup; if(mAnimation->hasAnimation(weapMovementAnimName)) movementAnimName = weapMovementAnimName; else - movemask = MWRender::Animation::BlendMask_LowerBody; + movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); } if (!mAnimation->hasAnimation(movementAnimName)) @@ -598,7 +617,7 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character } } -void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force) +void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force) { // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), // the idle animation should be displayed @@ -630,11 +649,13 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat else if(mIdleState != CharState_None) { idleGroup = "idle"; - if(weap != sWeaponTypeListEnd) + if(!weapShortGroup.empty()) { - idleGroup += weap->shortgroup; + idleGroup += weapShortGroup; if(!mAnimation->hasAnimation(idleGroup)) - idleGroup = "idle"; + { + idleGroup = fallbackShortWeaponGroup("idle"); + } // play until the Loop Stop key 2 to 5 times, then play until the Stop key // this replicates original engine behavior for the "Idle1h" 1st-person animation @@ -669,9 +690,9 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if (mPtr.getClass().isActor()) refreshHitRecoilAnims(idle); - const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); - if (!mPtr.getClass().hasInventoryStore(mPtr)) - weap = sWeaponTypeListEnd; + std::string weap; + if (mPtr.getClass().hasInventoryStore(mPtr)) + weap = getWeaponType(mWeaponType)->mShortGroup; refreshJumpAnims(weap, jump, idle, force); refreshMovementAnims(weap, movement, idle, force); @@ -680,77 +701,6 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat refreshIdleAnims(weap, idle, force); } - -void getWeaponGroup(WeaponType weaptype, std::string &group) -{ - const WeaponInfo *info = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(weaptype)); - if(info != sWeaponTypeListEnd) - group = info->longgroup; - else - group.clear(); -} - - -MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype) -{ - if(stats.getDrawState() == DrawState_Spell) - { - *weaptype = WeapType_Spell; - return inv.end(); - } - - if(stats.getDrawState() == MWMechanics::DrawState_Weapon) - { - MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end()) - *weaptype = WeapType_HandToHand; - else - { - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) - *weaptype = WeapType_PickProbe; - else if(type == typeid(ESM::Weapon).name()) - { - MWWorld::LiveCellRef *ref = weapon->get(); - ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; - switch(weaponType) - { - case ESM::Weapon::ShortBladeOneHand: - case ESM::Weapon::LongBladeOneHand: - case ESM::Weapon::BluntOneHand: - case ESM::Weapon::AxeOneHand: - case ESM::Weapon::Arrow: - case ESM::Weapon::Bolt: - *weaptype = WeapType_OneHand; - break; - case ESM::Weapon::LongBladeTwoHand: - case ESM::Weapon::BluntTwoClose: - case ESM::Weapon::AxeTwoHand: - *weaptype = WeapType_TwoHand; - break; - case ESM::Weapon::BluntTwoWide: - case ESM::Weapon::SpearTwoWide: - *weaptype = WeapType_TwoWide; - break; - case ESM::Weapon::MarksmanBow: - *weaptype = WeapType_BowAndArrow; - break; - case ESM::Weapon::MarksmanCrossbow: - *weaptype = WeapType_Crossbow; - break; - case ESM::Weapon::MarksmanThrown: - *weaptype = WeapType_Thrown; - break; - } - } - } - - return weapon; - } - - return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); -} - void CharacterController::playDeath(float startpoint, CharacterState death) { // Make sure the character was swimming upon death for forward-compatibility @@ -881,7 +831,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mHitState(CharState_None) , mUpperBodyState(UpperCharState_Nothing) , mJumpState(JumpState_None) - , mWeaponType(WeapType_None) + , mWeaponType(ESM::Weapon::None) , mAttackStrength(0.f) , mSkipAnim(false) , mSecondsOfSwimming(0) @@ -905,19 +855,20 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim if (cls.hasInventoryStore(mPtr)) { - getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); - if (mWeaponType != WeapType_None) + getActiveWeapon(mPtr, &mWeaponType); + if (mWeaponType != ESM::Weapon::None) { mUpperBodyState = UpperCharState_WeapEquiped; - getWeaponGroup(mWeaponType, mCurrentWeapon); + mCurrentWeapon = getWeaponAnimation(mWeaponType); } - if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) + if(mWeaponType != ESM::Weapon::None && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::HandToHand) { mAnimation->showWeapons(true); // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly, // for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example) - bool useRelativeDuration = mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Crossbow; + ESM::WeaponType::Class weaponClass = getWeaponType(mWeaponType)->mWeaponClass; + bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged; mAnimation->setWeaponGroup(mCurrentWeapon, useRelativeDuration); } @@ -1157,11 +1108,11 @@ bool CharacterController::updateCreatureState() const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); - WeaponType weapType = WeapType_None; + int weapType = ESM::Weapon::None; if(stats.getDrawState() == DrawState_Weapon) - weapType = WeapType_HandToHand; + weapType = ESM::Weapon::HandToHand; else if (stats.getDrawState() == DrawState_Spell) - weapType = WeapType_Spell; + weapType = ESM::Weapon::Spell; if (weapType != mWeaponType) { @@ -1178,7 +1129,7 @@ bool CharacterController::updateCreatureState() std::string startKey = "start"; std::string stopKey = "stop"; - if (weapType == WeapType_Spell) + if (weapType == ESM::Weapon::Spell) { const std::string spellid = stats.getSpells().getSelectedSpell(); bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr); @@ -1214,7 +1165,7 @@ bool CharacterController::updateCreatureState() mCurrentWeapon = ""; } - if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation + if (weapType != ESM::Weapon::Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation { mCurrentWeapon = chooseRandomAttackAnimation(); } @@ -1229,7 +1180,7 @@ bool CharacterController::updateCreatureState() mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); - if (weapType == WeapType_HandToHand) + if (weapType == ESM::Weapon::HandToHand) playSwishSound(0.0f); } } @@ -1243,34 +1194,23 @@ bool CharacterController::updateCreatureState() return false; } -bool CharacterController::updateCarriedLeftVisible(WeaponType weaptype) const +bool CharacterController::updateCarriedLeftVisible(const int weaptype) const { // Shields/torches shouldn't be visible during any operation involving two hands // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", // but they are also present in weapon drawing animation. - switch (weaptype) - { - case WeapType_Spell: - case WeapType_BowAndArrow: - case WeapType_Crossbow: - case WeapType_HandToHand: - case WeapType_TwoHand: - case WeapType_TwoWide: - return false; - default: - return true; - } + return !(getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded); } bool CharacterController::updateWeaponState(CharacterState& idle) { const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); - WeaponType weaptype = WeapType_None; + int weaptype = ESM::Weapon::None; if(stats.getDrawState() == DrawState_Weapon) - weaptype = WeapType_HandToHand; + weaptype = ESM::Weapon::HandToHand; else if (stats.getDrawState() == DrawState_Spell) - weaptype = WeapType_Spell; + weaptype = ESM::Weapon::Spell; const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf(); @@ -1280,18 +1220,18 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if (mPtr.getClass().hasInventoryStore(mPtr)) { MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); + MWWorld::ContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); if(stats.getDrawState() == DrawState_Spell) weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon != inv.end() && mWeaponType != WeapType_HandToHand && weaptype > WeapType_HandToHand && weaptype < WeapType_Spell) + if(weapon != inv.end() && mWeaponType != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None) upSoundId = weapon->getClass().getUpSoundId(*weapon); - if(weapon != inv.end() && mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell) + if(weapon != inv.end() && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None) downSoundId = weapon->getClass().getDownSoundId(*weapon); // weapon->HtH switch: weapon is empty already, so we need to take sound from previous weapon - if(weapon == inv.end() && !mWeapon.isEmpty() && weaptype == WeapType_HandToHand && mWeaponType != WeapType_Spell) + if(weapon == inv.end() && !mWeapon.isEmpty() && weaptype == ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell) downSoundId = mWeapon.getClass().getDownSoundId(mWeapon); MWWorld::Ptr newWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr(); @@ -1312,8 +1252,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle) bool forcestateupdate = false; // We should not play equipping animation and sound during weapon->weapon transition - bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell && - mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell; + bool isStillWeapon = weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None && + mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None; // If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound spell expires), // we should force actor to the "weapon equipped" state, interrupt attack and update animations. @@ -1331,7 +1271,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if(!isKnockedOut() && !isKnockedDown() && !isRecovery()) { std::string weapgroup; - if ((!isWerewolf || mWeaponType != WeapType_Spell) + if ((!isWerewolf || mWeaponType != ESM::Weapon::Spell) && weaptype != mWeaponType && mUpperBodyState != UpperCharState_UnEquipingWeap && !isStillWeapon) @@ -1340,7 +1280,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if (!weaponChanged) { // Note: we do not disable unequipping animation automatically to avoid body desync - getWeaponGroup(mWeaponType, weapgroup); + weapgroup = getWeaponAnimation(mWeaponType); mAnimation->play(weapgroup, priorityWeapon, MWRender::Animation::BlendMask_All, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0); @@ -1368,17 +1308,18 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { forcestateupdate = true; mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); + weapgroup = getWeaponAnimation(weaptype); - getWeaponGroup(weaptype, weapgroup); // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly, // for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example) - bool useRelativeDuration = weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow; + ESM::WeaponType::Class weaponClass = getWeaponType(weaptype)->mWeaponClass; + bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged; mAnimation->setWeaponGroup(weapgroup, useRelativeDuration); if (!isStillWeapon) { mAnimation->disable(mCurrentWeapon); - if (weaptype != WeapType_None) + if (weaptype != ESM::Weapon::None) { mAnimation->showWeapons(false); mAnimation->play(weapgroup, priorityWeapon, @@ -1387,7 +1328,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) mUpperBodyState = UpperCharState_EquipingWeap; // If we do not have the "equip attach" key, show weapon manually. - if (weaptype != WeapType_Spell) + if (weaptype != ESM::Weapon::Spell) { if (mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0) mAnimation->showWeapons(true); @@ -1407,7 +1348,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) } mWeaponType = weaptype; - getWeaponGroup(mWeaponType, mCurrentWeapon); + mCurrentWeapon = getWeaponAnimation(mWeaponType); if(!upSoundId.empty() && !isStillWeapon) { @@ -1421,8 +1362,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { mUpperBodyState = UpperCharState_Nothing; mAnimation->disable(mCurrentWeapon); - mWeaponType = WeapType_None; - getWeaponGroup(mWeaponType, mCurrentWeapon); + mWeaponType = ESM::Weapon::None; + mCurrentWeapon = getWeaponAnimation(mWeaponType); } } } @@ -1433,7 +1374,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && mHasMovedInXY && !MWBase::Environment::get().getWorld()->isSwimming(mPtr) - && mWeaponType == WeapType_None) + && mWeaponType == ESM::Weapon::None) { if(!sndMgr->getSoundPlaying(mPtr, "WolfRun")) sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWSound::Type::Sfx, @@ -1450,16 +1391,17 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if (mPtr.getClass().hasInventoryStore(mPtr)) { MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); + MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()); - if(isWeapon) + if (isWeapon) + { weapSpeed = weapon->get()->mBase->mData.mSpeed; + MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + int ammotype = getWeaponType(weapon->get()->mBase->mData.mType)->mAmmoType; + if (ammotype != ESM::Weapon::None && (ammo == inv.end() || ammo->get()->mBase->mData.mType != ammotype)) + ammunition = false; + } - MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (mWeaponType == WeapType_Crossbow) - ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt); - else if (mWeaponType == WeapType_BowAndArrow) - ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow); if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) { mAnimation->disable(mCurrentWeapon); @@ -1473,6 +1415,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) float complete; bool animPlaying; + ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; if(mAttackingOrSpell) { MWWorld::Ptr player = getPlayer(); @@ -1491,7 +1434,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) mCurrentWeapon = chooseRandomAttackAnimation(); } - if(mWeaponType == WeapType_Spell) + if(mWeaponType == ESM::Weapon::Spell) { // Unset casting flag, otherwise pressing the mouse button down would // continue casting every frame if there is no animation @@ -1597,7 +1540,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) resetIdle = false; } } - else if(mWeaponType == WeapType_PickProbe) + else if(mWeaponType == ESM::Weapon::PickProbe) { MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::Ptr item = *weapon; @@ -1627,7 +1570,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { std::string startKey; std::string stopKey; - if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown) + + if(weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) { mAttackType = "shoot"; startKey = mAttackType+" start"; @@ -1699,7 +1643,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); } - if(mWeaponType != WeapType_Crossbow && mWeaponType != WeapType_BowAndArrow) + if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); @@ -1731,7 +1675,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if (mUpperBodyState > UpperCharState_WeapEquiped) { mUpperBodyState = UpperCharState_WeapEquiped; - if (mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell) + if (mWeaponType > ESM::Weapon::None) mAnimation->showWeapons(true); } mAnimation->disable(mCurrentWeapon); @@ -1739,9 +1683,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) } mAnimation->setPitchFactor(0.f); - if (mWeaponType == WeapType_BowAndArrow || - mWeaponType == WeapType_Thrown || - mWeaponType == WeapType_Crossbow) + if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) { switch (mUpperBodyState) { @@ -1758,7 +1700,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { // technically we do not need a pitch for crossbow reload animation, // but we should avoid abrupt repositioning - if (mWeaponType == WeapType_Crossbow) + if (mWeaponType == ESM::Weapon::MarksmanCrossbow) mAnimation->setPitchFactor(std::max(0.f, 1.f-complete*10.f)); else mAnimation->setPitchFactor(1.f-complete); @@ -1775,7 +1717,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) mUpperBodyState == UpperCharState_FollowStartToFollowStop || mUpperBodyState == UpperCharState_CastingSpell) { - if (ammunition && mWeaponType == WeapType_Crossbow) + if (ammunition && mWeaponType == ESM::Weapon::MarksmanCrossbow) mAnimation->attachArrow(); mUpperBodyState = UpperCharState_WeapEquiped; @@ -1852,7 +1794,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if(!start.empty()) { int mask = MWRender::Animation::BlendMask_All; - if (mWeaponType == WeapType_Crossbow) + if (mWeaponType == ESM::Weapon::MarksmanCrossbow) mask = MWRender::Animation::BlendMask_UpperBody; mAnimation->disable(mCurrentWeapon); @@ -2639,7 +2581,7 @@ void CharacterController::resurrect() mAnimation->disable(mCurrentDeath); mCurrentDeath.clear(); mDeathState = CharState_None; - mWeaponType = WeapType_None; + mWeaponType = ESM::Weapon::None; } void CharacterController::updateContinuousVfx() diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index f2b1f33a7..614beca1c 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -10,6 +10,8 @@ #include "../mwrender/animation.hpp" +#include "weapontype.hpp" + namespace MWWorld { class InventoryStore; @@ -113,21 +115,6 @@ enum CharacterState { CharState_Block }; -enum WeaponType { - WeapType_None, - - WeapType_HandToHand, - WeapType_OneHand, - WeapType_TwoHand, - WeapType_TwoWide, - WeapType_BowAndArrow, - WeapType_Crossbow, - WeapType_Thrown, - WeapType_PickProbe, - - WeapType_Spell -}; - enum UpperBodyCharacterState { UpperCharState_Nothing, UpperCharState_EquipingWeap, @@ -186,7 +173,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener JumpingState mJumpState; std::string mCurrentJump; - WeaponType mWeaponType; + int mWeaponType; std::string mCurrentWeapon; float mAttackStrength; @@ -212,9 +199,9 @@ class CharacterController : public MWRender::Animation::TextKeyListener void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); void refreshHitRecoilAnims(CharacterState& idle); - void refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, bool force=false); - void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, CharacterState& idle, bool force=false); - void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false); + void refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force=false); + void refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force=false); + void refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force=false); void clearAnimQueue(bool clearPersistAnims = false); @@ -241,7 +228,11 @@ class CharacterController : public MWRender::Animation::TextKeyListener /// @param num if non-nullptr, the chosen animation number will be written here std::string chooseRandomGroup (const std::string& prefix, int* num = nullptr) const; - bool updateCarriedLeftVisible(WeaponType weaptype) const; + bool updateCarriedLeftVisible(int weaptype) const; + + std::string fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask = nullptr); + + std::string getWeaponAnimation(int weaponType) const; public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); @@ -312,8 +303,6 @@ public: void playSwishSound(float attackStrength); }; - - MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype); } #endif /* GAME_MWMECHANICS_CHARACTER_HPP */ diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index e7386afe8..f8dd11a0e 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -26,6 +26,7 @@ #include "npcstats.hpp" #include "actorutil.hpp" #include "aifollow.hpp" +#include "weapontype.hpp" namespace MWMechanics { @@ -826,8 +827,9 @@ namespace MWMechanics bool isProjectile = false; if (item.getTypeName() == typeid(ESM::Weapon).name()) { - const ESM::Weapon* ref = item.get()->mBase; - isProjectile = ref->mData.mType >= ESM::Weapon::MarksmanThrown; + int type = item.get()->mBase->mData.mType; + ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass; + isProjectile = (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ranged); } int type = enchantment->mData.mType; diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index bff261e67..7b5c38592 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -16,6 +16,8 @@ #include "creaturestats.hpp" #include "spellcasting.hpp" +#include "weapontype.hpp" +#include "combat.hpp" namespace { @@ -376,7 +378,7 @@ namespace MWMechanics case ESM::MagicEffect::BoundLongbow: // AI should not summon the bow if there is no suitable ammo. - if (rateAmmo(actor, enemy, ESM::Weapon::Arrow) <= 0.f) + if (rateAmmo(actor, enemy, getWeaponType(ESM::Weapon::MarksmanBow)->mAmmoType) <= 0.f) return 0.f; break; diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 37f37d4c7..2e6501225 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -14,6 +14,7 @@ #include "aicombataction.hpp" #include "spellpriority.hpp" #include "spellcasting.hpp" +#include "weapontype.hpp" namespace MWMechanics { @@ -34,14 +35,15 @@ namespace MWMechanics const MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::Store& gmst = world->getStore().get(); - if (type == -1 && (weapon->mData.mType == ESM::Weapon::Arrow || weapon->mData.mType == ESM::Weapon::Bolt)) + ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(weapon->mData.mType)->mWeaponClass; + if (type == -1 && weapclass == ESM::WeaponType::Ammo) return 0.f; float rating=0.f; static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->mValue.getFloat(); float ratingMult = fAIMeleeWeaponMult; - if (weapon->mData.mType >= ESM::Weapon::MarksmanBow && weapon->mData.mType <= ESM::Weapon::MarksmanThrown) + if (weapclass != ESM::WeaponType::Melee) { // Underwater ranged combat is impossible if (world->isUnderwater(MWWorld::ConstPtr(actor), 0.75f) @@ -59,11 +61,11 @@ namespace MWMechanics const float chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f; // We need to account for the fact that thrown weapons have 2x real damage applied to the target // as they're both the weapon and the ammo of the hit - if (weapon->mData.mType == ESM::Weapon::MarksmanThrown) + if (weapclass == ESM::WeaponType::Thrown) { rating = chop * 2; } - else if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) + else if (weapclass != ESM::WeaponType::Melee) { rating = chop; } @@ -76,24 +78,28 @@ namespace MWMechanics adjustWeaponDamage(rating, item, actor); - if (weapon->mData.mType != ESM::Weapon::MarksmanBow && weapon->mData.mType != ESM::Weapon::MarksmanCrossbow) + if (weapclass != ESM::WeaponType::Ranged) { resistNormalWeapon(enemy, actor, item, rating); applyWerewolfDamageMult(enemy, item, rating); } - else if (weapon->mData.mType == ESM::Weapon::MarksmanBow) - { - if (arrowRating <= 0.f) - rating = 0.f; - else - rating += arrowRating; - } - else if (weapon->mData.mType == ESM::Weapon::MarksmanCrossbow) + else { - if (boltRating <= 0.f) - rating = 0.f; - else - rating += boltRating; + int ammotype = MWMechanics::getWeaponType(weapon->mData.mType)->mAmmoType; + if (ammotype == ESM::Weapon::Arrow) + { + if (arrowRating <= 0.f) + rating = 0.f; + else + rating += arrowRating; + } + else if (ammotype == ESM::Weapon::Bolt) + { + if (boltRating <= 0.f) + rating = 0.f; + else + rating += boltRating; + } } if (!weapon->mEnchant.empty()) @@ -104,7 +110,7 @@ namespace MWMechanics int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); float charge = item.getCellRef().getEnchantmentCharge(); - if (charge == -1 || charge >= castCost || weapon->mData.mType >= ESM::Weapon::MarksmanThrown) + if (charge == -1 || charge >= castCost || weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo) rating += rateEffects(enchantment->mEffects, actor, enemy); } } @@ -126,13 +132,13 @@ namespace MWMechanics float chance = getHitChance(actor, enemy, value) / 100.f; rating *= std::min(1.f, std::max(0.01f, chance)); - if (weapon->mData.mType < ESM::Weapon::Arrow) + if (weapclass != ESM::WeaponType::Ammo) rating *= weapon->mData.mSpeed; return rating * ratingMult; } - float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType) + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, int ammoType) { float bestAmmoRating = 0.f; if (!actor.getClass().hasInventoryStore(actor)) @@ -153,7 +159,7 @@ namespace MWMechanics return bestAmmoRating; } - float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, ESM::Weapon::Type ammoType) + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, int ammoType) { MWWorld::Ptr emptyPtr; return rateAmmo(actor, enemy, emptyPtr, ammoType); @@ -175,8 +181,8 @@ namespace MWMechanics float bonusDamage = 0.f; const ESM::Weapon* esmWeap = weapon.get()->mBase; - - if (esmWeap->mData.mType >= ESM::Weapon::MarksmanBow) + int type = esmWeap->mData.mType; + if (getWeaponType(type)->mWeaponClass != ESM::WeaponType::Melee) { if (!ammo.isEmpty() && !MWBase::Environment::get().getWorld()->isSwimming(enemy)) { diff --git a/apps/openmw/mwmechanics/weaponpriority.hpp b/apps/openmw/mwmechanics/weaponpriority.hpp index f5c9a1159..67de7b50f 100644 --- a/apps/openmw/mwmechanics/weaponpriority.hpp +++ b/apps/openmw/mwmechanics/weaponpriority.hpp @@ -10,8 +10,8 @@ namespace MWMechanics float rateWeapon (const MWWorld::Ptr& item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type=-1, float arrowRating=0.f, float boltRating=0.f); - float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType); - float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, ESM::Weapon::Type ammoType); + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, int ammoType); + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, int ammoType); float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); } diff --git a/apps/openmw/mwmechanics/weapontype.cpp b/apps/openmw/mwmechanics/weapontype.cpp new file mode 100644 index 000000000..07345557f --- /dev/null +++ b/apps/openmw/mwmechanics/weapontype.cpp @@ -0,0 +1,53 @@ +#include "weapontype.hpp" + +#include "../mwworld/class.hpp" + +namespace MWMechanics +{ + static const ESM::WeaponType *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])]; + + MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype) + { + MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor); + CreatureStats &stats = actor.getClass().getCreatureStats(actor); + if(stats.getDrawState() == MWMechanics::DrawState_Spell) + { + *weaptype = ESM::Weapon::Spell; + return inv.end(); + } + + if(stats.getDrawState() == MWMechanics::DrawState_Weapon) + { + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon == inv.end()) + *weaptype = ESM::Weapon::HandToHand; + else + { + const std::string &type = weapon->getTypeName(); + if(type == typeid(ESM::Weapon).name()) + { + const MWWorld::LiveCellRef *ref = weapon->get(); + *weaptype = ref->mBase->mData.mType; + } + else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) + *weaptype = ESM::Weapon::PickProbe; + } + + return weapon; + } + + return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + } + + const ESM::WeaponType* getWeaponType(const int weaponType) + { + std::map::const_iterator found = sWeaponTypeList.find(weaponType); + if (found == sWeaponTypeList.end()) + { + // Use one-handed short blades as fallback + return &sWeaponTypeList[0]; + } + + return &found->second; + } +} diff --git a/apps/openmw/mwmechanics/weapontype.hpp b/apps/openmw/mwmechanics/weapontype.hpp new file mode 100644 index 000000000..344dab1ed --- /dev/null +++ b/apps/openmw/mwmechanics/weapontype.hpp @@ -0,0 +1,271 @@ +#ifndef GAME_MWMECHANICS_WEAPONTYPE_H +#define GAME_MWMECHANICS_WEAPONTYPE_H + +#include "../mwworld/inventorystore.hpp" + +#include "creaturestats.hpp" + +namespace MWMechanics +{ + static std::map sWeaponTypeList = + { + { + ESM::Weapon::None, + { + /* short group */ "", + /* long group */ "", + /* sound ID */ "", + /* attach bone */ "", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::HandToHand, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 + } + }, + { + ESM::Weapon::PickProbe, + { + /* short group */ "1h", + /* long group */ "pickprobe", + /* sound ID */ "", + /* attach bone */ "", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::Security, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 + } + }, + { + ESM::Weapon::Spell, + { + /* short group */ "spell", + /* long group */ "spellcast", + /* sound ID */ "", + /* attach bone */ "", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::HandToHand, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::HandToHand, + { + /* short group */ "hh", + /* long group */ "handtohand", + /* sound ID */ "", + /* attach bone */ "", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::HandToHand, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::ShortBladeOneHand, + { + /* short group */ "1s", + /* long group */ "shortbladeonehand", + /* sound ID */ "Item Weapon Shortblade", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 ShortBladeOneHand", + /* usage skill */ ESM::Skill::ShortBlade, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth + } + }, + { + ESM::Weapon::LongBladeOneHand, + { + /* short group */ "1h", + /* long group */ "weapononehand", + /* sound ID */ "Item Weapon Longblade", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 LongBladeOneHand", + /* usage skill */ ESM::Skill::LongBlade, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth + } + }, + { + ESM::Weapon::BluntOneHand, + { + /* short group */ "1b", + /* long group */ "bluntonehand", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 BluntOneHand", + /* usage skill */ ESM::Skill::BluntWeapon, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth + } + }, + { + ESM::Weapon::AxeOneHand, + { + /* short group */ "1b", + /* long group */ "bluntonehand", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 LongBladeOneHand", + /* usage skill */ ESM::Skill::Axe, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth + } + }, + { + ESM::Weapon::LongBladeTwoHand, + { + /* short group */ "2c", + /* long group */ "weapontwohand", + /* sound ID */ "Item Weapon Longblade", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 LongBladeTwoClose", + /* usage skill */ ESM::Skill::LongBlade, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::AxeTwoHand, + { + /* short group */ "2b", + /* long group */ "bluntwohand", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 AxeTwoClose", + /* usage skill */ ESM::Skill::Axe, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::BluntTwoClose, + { + /* short group */ "2b", + /* long group */ "bluntwohand", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 BluntTwoClose", + /* usage skill */ ESM::Skill::BluntWeapon, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::BluntTwoWide, + { + /* short group */ "2w", + /* long group */ "weapontwowide", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 BluntTwoWide", + /* usage skill */ ESM::Skill::BluntWeapon, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::SpearTwoWide, + { + /* short group */ "2w", + /* long group */ "weapontwowide", + /* sound ID */ "Item Weapon Spear", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 SpearTwoWide", + /* usage skill */ ESM::Skill::Spear, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::MarksmanBow, + { + /* short group */ "bow", + /* long group */ "bowandarrow", + /* sound ID */ "Item Weapon Bow", + /* attach bone */ "Weapon Bone Left", + /* sheath bone */ "Bip01 MarksmanBow", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Ranged, + /* ammo type */ ESM::Weapon::Arrow, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::MarksmanCrossbow, + { + /* short group */ "crossbow", + /* long group */ "crossbow", + /* sound ID */ "Item Weapon Crossbow", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 MarksmanCrossbow", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Ranged, + /* ammo type */ ESM::Weapon::Bolt, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::MarksmanThrown, + { + /* short group */ "1h", + /* long group */ "throwweapon", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 MarksmanThrown", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Thrown, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 + } + }, + { + ESM::Weapon::Arrow, + { + /* short group */ "", + /* long group */ "", + /* sound ID */ "Item Weapon Ammo", + /* attach bone */ "ArrowBone", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Ammo, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 + } + }, + { + ESM::Weapon::Bolt, + { + /* short group */ "", + /* long group */ "", + /* sound ID */ "Item Weapon Ammo", + /* attach bone */ "ArrowBone", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Ammo, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 + } + } + }; + + MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype); + + const ESM::WeaponType* getWeaponType(const int weaponType); +} + +#endif diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 8eac63b2f..330c5a678 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -28,6 +28,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/weapontype.hpp" #include "vismask.hpp" @@ -51,8 +52,6 @@ ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr // Make sure we cleaned object from effects, just in cast if we re-use node removeEffects(); - - mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); } ActorAnimation::~ActorAnimation() @@ -84,7 +83,7 @@ PartHolderPtr ActorAnimation::getWeaponPart(const std::string& model, const std: return PartHolderPtr(new PartHolder(instance)); } -osg::Group* ActorAnimation::getBoneByName(std::string boneName) +osg::Group* ActorAnimation::getBoneByName(const std::string& boneName) { if (!mObjectRoot) return nullptr; @@ -105,93 +104,13 @@ std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& if(type == typeid(ESM::Weapon).name()) { const MWWorld::LiveCellRef *ref = weapon.get(); - ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; - return getHolsteredWeaponBoneName(weaponType); - } - - return boneName; -} - -std::string ActorAnimation::getHolsteredWeaponBoneName(const unsigned int weaponType) -{ - std::string boneName; - - switch(weaponType) - { - case ESM::Weapon::ShortBladeOneHand: - boneName = "Bip01 ShortBladeOneHand"; - break; - case ESM::Weapon::LongBladeOneHand: - boneName = "Bip01 LongBladeOneHand"; - break; - case ESM::Weapon::BluntOneHand: - boneName = "Bip01 BluntOneHand"; - break; - case ESM::Weapon::AxeOneHand: - boneName = "Bip01 LongBladeOneHand"; - break; - case ESM::Weapon::LongBladeTwoHand: - boneName = "Bip01 LongBladeTwoClose"; - break; - case ESM::Weapon::BluntTwoClose: - boneName = "Bip01 BluntTwoClose"; - break; - case ESM::Weapon::AxeTwoHand: - boneName = "Bip01 AxeTwoClose"; - break; - case ESM::Weapon::BluntTwoWide: - boneName = "Bip01 BluntTwoWide"; - break; - case ESM::Weapon::SpearTwoWide: - boneName = "Bip01 SpearTwoWide"; - break; - case ESM::Weapon::MarksmanBow: - boneName = "Bip01 MarksmanBow"; - break; - case ESM::Weapon::MarksmanCrossbow: - boneName = "Bip01 MarksmanCrossbow"; - break; - case ESM::Weapon::MarksmanThrown: - boneName = "Bip01 MarksmanThrown"; - break; - default: - break; + int weaponType = ref->mBase->mData.mType; + return MWMechanics::getWeaponType(weaponType)->mSheathingBone; } return boneName; } -void ActorAnimation::injectWeaponBones() -{ - if (!mResourceSystem->getVFS()->exists("meshes\\xbase_anim_sh.nif")) - { - mWeaponSheathing = false; - return; - } - - osg::ref_ptr sheathSkeleton = mResourceSystem->getSceneManager()->getInstance("meshes\\xbase_anim_sh.nif"); - - for (unsigned int type=0; type<=ESM::Weapon::MarksmanThrown; ++type) - { - const std::string holsteredBoneName = getHolsteredWeaponBoneName(type); - - SceneUtil::FindByNameVisitor findVisitor (holsteredBoneName); - sheathSkeleton->accept(findVisitor); - osg::ref_ptr sheathNode = findVisitor.mFoundNode; - - if (sheathNode && sheathNode.get()->getNumParents()) - { - osg::Group* sheathParent = getBoneByName(sheathNode.get()->getParent(0)->getName()); - - if (sheathParent) - { - sheathNode.get()->getParent(0)->removeChild(sheathNode); - sheathParent->addChild(sheathNode); - } - } - } -} - void ActorAnimation::resetControllers(osg::Node* node) { if (node == nullptr) @@ -205,7 +124,8 @@ void ActorAnimation::resetControllers(osg::Node* node) void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) { - if (!mWeaponSheathing) + static const bool weaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); + if (!weaponSheathing) return; if (!mPtr.getClass().hasInventoryStore(mPtr)) @@ -219,7 +139,8 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) return; // Since throwing weapons stack themselves, do not show such weapon itself - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + int type = weapon->get()->mBase->mData.mType; + if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) showHolsteredWeapons = false; std::string mesh = weapon->getClass().getModel(*weapon); @@ -279,7 +200,8 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) void ActorAnimation::updateQuiver() { - if (!mWeaponSheathing) + static const bool weaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); + if (!weaponSheathing) return; if (!mPtr.getClass().hasInventoryStore(mPtr)) @@ -303,10 +225,12 @@ void ActorAnimation::updateQuiver() bool suitableAmmo = false; MWWorld::ConstContainerStoreIterator ammo = weapon; unsigned int ammoCount = 0; - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + int type = weapon->get()->mBase->mData.mType; + const auto& weaponType = MWMechanics::getWeaponType(type); + if (weaponType->mWeaponClass == ESM::WeaponType::Thrown) { ammoCount = ammo->getRefData().getCount(); - osg::Group* throwingWeaponNode = getBoneByName("Weapon Bone"); + osg::Group* throwingWeaponNode = getBoneByName(weaponType->mAttachBone); if (throwingWeaponNode && throwingWeaponNode->getNumChildren()) ammoCount--; @@ -323,10 +247,7 @@ void ActorAnimation::updateQuiver() if (arrowAttached) ammoCount--; - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) - suitableAmmo = ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt; - else if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanBow) - suitableAmmo = ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow; + suitableAmmo = ammo->get()->mBase->mData.mType == weaponType->mAmmoType; } if (!suitableAmmo) @@ -382,7 +303,8 @@ void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/) return; MWWorld::ConstContainerStoreIterator ammo = inv.end(); - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + int type = weapon->get()->mBase->mData.mType; + if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) ammo = weapon; else ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); @@ -415,7 +337,8 @@ void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/) return; MWWorld::ConstContainerStoreIterator ammo = inv.end(); - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + int type = weapon->get()->mBase->mData.mType; + if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) ammo = weapon; else ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index 2146a0260..f1f6f6ca8 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -40,13 +40,10 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener virtual bool isArrowAttached() const { return false; } protected: - bool mWeaponSheathing; - osg::Group* getBoneByName(std::string boneName); + osg::Group* getBoneByName(const std::string& boneName); virtual void updateHolsteredWeapon(bool showHolsteredWeapons); - virtual void injectWeaponBones(); virtual void updateQuiver(); virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon); - virtual std::string getHolsteredWeaponBoneName(const unsigned int weaponType); virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor); virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename) { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 8f8d7e245..ff09c7aef 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -234,6 +234,25 @@ namespace std::vector > mToRemove; }; + class GetExtendedBonesVisitor : public osg::NodeVisitor + { + public: + GetExtendedBonesVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + void apply(osg::Node& node) + { + if (SceneUtil::hasUserDescription(&node, "CustomBone")) + mFoundBones.emplace_back(&node, node.getParent(0)); + + traverse(node); + } + + std::vector > mFoundBones; + }; + class RemoveFinishedCallbackVisitor : public RemoveVisitor { public: @@ -1336,8 +1355,66 @@ namespace MWRender state->second.mLoopingEnabled = enabled; } - osg::ref_ptr getModelInstance(Resource::SceneManager* sceneMgr, const std::string& model, bool baseonly) + void loadBonesFromFile(osg::ref_ptr& baseNode, const std::string &model, Resource::ResourceSystem* resourceSystem) + { + const osg::Node* node = resourceSystem->getSceneManager()->getTemplate(model).get(); + osg::ref_ptr sheathSkeleton (const_cast(node)); // const-trickery required because there is no const version of NodeVisitor + + GetExtendedBonesVisitor getBonesVisitor; + sheathSkeleton->accept(getBonesVisitor); + for (auto& nodePair : getBonesVisitor.mFoundBones) + { + SceneUtil::FindByNameVisitor findVisitor (nodePair.second->getName()); + baseNode->accept(findVisitor); + + osg::Group* sheathParent = findVisitor.mFoundNode; + if (sheathParent) + { + osg::Node* copy = osg::clone(nodePair.first, osg::CopyOp::DEEP_COPY_NODES); + sheathParent->addChild(copy); + } + } + } + + void injectCustomBones(osg::ref_ptr& node, const std::string& model, Resource::ResourceSystem* resourceSystem) { + const std::map& index = resourceSystem->getVFS()->getIndex(); + + std::string animationPath = model; + if (animationPath.find("meshes") == 0) + { + animationPath.replace(0, 6, "animations"); + } + animationPath.replace(animationPath.size()-4, 4, "/"); + + resourceSystem->getVFS()->normalizeFilename(animationPath); + + std::map::const_iterator found = index.lower_bound(animationPath); + while (found != index.end()) + { + const std::string& name = found->first; + if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath) + { + size_t pos = name.find_last_of('.'); + if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".nif") == 0) + loadBonesFromFile(node, name, resourceSystem); + } + else + break; + ++found; + } + } + + enum InjectType + { + None, + Model, + ModelWithFallback + }; + + osg::ref_ptr getModelInstance(Resource::ResourceSystem* resourceSystem, const std::string& model, bool baseonly, InjectType inject) + { + Resource::SceneManager* sceneMgr = resourceSystem->getSceneManager(); if (baseonly) { typedef std::map > Cache; @@ -1347,6 +1424,12 @@ namespace MWRender { osg::ref_ptr created = sceneMgr->getInstance(model); + if (inject == InjectType::ModelWithFallback) + injectCustomBones(created, "meshes\\xbase_anim.nif", resourceSystem); + + if (inject != InjectType::None) + injectCustomBones(created, model, resourceSystem); + SceneUtil::CleanObjectRootVisitor removeDrawableVisitor; created->accept(removeDrawableVisitor); removeDrawableVisitor.remove(); @@ -1359,7 +1442,17 @@ namespace MWRender return sceneMgr->createInstance(found->second); } else - return sceneMgr->getInstance(model); + { + osg::ref_ptr created = sceneMgr->getInstance(model); + + if (inject == InjectType::ModelWithFallback) + injectCustomBones(created, "meshes\\xbase_anim.nif", resourceSystem); + + if (inject != InjectType::None) + injectCustomBones(created, model, resourceSystem); + + return created; + } } void Animation::setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature) @@ -1381,9 +1474,18 @@ namespace MWRender mAccumRoot = nullptr; mAccumCtrl = nullptr; + static const bool useAdditionalSources = Settings::Manager::getBool ("use additional anim sources", "Game"); + InjectType inject = useAdditionalSources && mPtr.getClass().isActor() ? InjectType::Model : InjectType::None; + if (inject != InjectType::None && isCreature) + { + MWWorld::LiveCellRef *ref = mPtr.get(); + if(ref->mBase->mFlags & ESM::Creature::Bipedal) + inject = InjectType::ModelWithFallback; + } + if (!forceskeleton) { - osg::ref_ptr created = getModelInstance(mResourceSystem->getSceneManager(), model, baseonly); + osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject); mInsert->addChild(created); mObjectRoot = created->asGroup(); if (!mObjectRoot) @@ -1399,7 +1501,7 @@ namespace MWRender } else { - osg::ref_ptr created = getModelInstance(mResourceSystem->getSceneManager(), model, baseonly); + osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject); osg::ref_ptr skel = dynamic_cast(created.get()); if (!skel) { diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index c4afdde9c..b2552e598 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -24,6 +24,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/weapontype.hpp" #include "npcanimation.hpp" #include "vismask.hpp" @@ -290,55 +291,36 @@ namespace MWRender MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter); MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - std::string groupname; + std::string groupname = "inventoryhandtohand"; bool showCarriedLeft = true; - if(iter == inv.end()) - groupname = "inventoryhandtohand"; - else + if(iter != inv.end()) { - const std::string &typeName = iter->getTypeName(); - if(typeName == typeid(ESM::Lockpick).name() || typeName == typeid(ESM::Probe).name()) - groupname = "inventoryweapononehand"; - else if(typeName == typeid(ESM::Weapon).name()) + groupname = "inventoryweapononehand"; + if(iter->getTypeName() == typeid(ESM::Weapon).name()) { MWWorld::LiveCellRef *ref = iter->get(); - int type = ref->mBase->mData.mType; - if(type == ESM::Weapon::ShortBladeOneHand || - type == ESM::Weapon::LongBladeOneHand || - type == ESM::Weapon::BluntOneHand || - type == ESM::Weapon::AxeOneHand || - type == ESM::Weapon::MarksmanThrown) - { - groupname = "inventoryweapononehand"; - } - else if(type == ESM::Weapon::MarksmanCrossbow || - type == ESM::Weapon::MarksmanBow) - { - groupname = "inventoryweapononehand"; - showCarriedLeft = false; - } - else if(type == ESM::Weapon::LongBladeTwoHand || - type == ESM::Weapon::BluntTwoClose || - type == ESM::Weapon::AxeTwoHand) - { - groupname = "inventoryweapontwohand"; - showCarriedLeft = false; - } - else if(type == ESM::Weapon::BluntTwoWide || - type == ESM::Weapon::SpearTwoWide) - { - groupname = "inventoryweapontwowide"; - showCarriedLeft = false; - } + const ESM::WeaponType* weaponInfo = MWMechanics::getWeaponType(type); + showCarriedLeft = !(weaponInfo->mFlags & ESM::WeaponType::TwoHanded); + + std::string inventoryGroup = weaponInfo->mLongGroup; + inventoryGroup = "inventory" + inventoryGroup; + + // We still should use one-handed animation as fallback + if (mAnimation->hasAnimation(inventoryGroup)) + groupname = inventoryGroup; else { - groupname = "inventoryhandtohand"; - showCarriedLeft = false; + static const std::string oneHandFallback = "inventory" + MWMechanics::getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup; + static const std::string twoHandFallback = "inventory" + MWMechanics::getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup; + + // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones + if (weaponInfo->mFlags & ESM::WeaponType::TwoHanded && weaponInfo->mWeaponClass == ESM::WeaponType::Melee) + groupname = twoHandFallback; + else + groupname = oneHandFallback; } } - else - groupname = "inventoryhandtohand"; } mAnimation->showCarriedLeft(showCarriedLeft); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index db88c3487..6bece05ec 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -15,6 +15,8 @@ #include "../mwbase/world.hpp" +#include "../mwmechanics/weapontype.hpp" + #include "../mwworld/class.hpp" namespace MWRender @@ -50,9 +52,6 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const if((ref->mBase->mFlags&ESM::Creature::Bipedal)) { - if (mWeaponSheathing) - injectWeaponBones(); - addAnimSource("meshes\\xbase_anim.nif", model); } addAnimSource(model, model); @@ -115,7 +114,22 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) std::string bonename; if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - bonename = "Weapon Bone"; + { + if(item.getTypeName() == typeid(ESM::Weapon).name()) + { + int type = item.get()->mBase->mData.mType; + bonename = MWMechanics::getWeaponType(type)->mAttachBone; + if (bonename != "Weapon Bone") + { + const NodeMap& nodeMap = getNodeMap(); + NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); + if (found == nodeMap.end()) + bonename = "Weapon Bone"; + } + } + else + bonename = "Weapon Bone"; + } else bonename = "Shield Bone"; @@ -140,8 +154,9 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) item.getTypeName() == typeid(ESM::Weapon).name() && item.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { + const ESM::WeaponType* weaponInfo = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow); MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) + if (ammo != inv.end() && ammo->get()->mBase->mData.mType == weaponInfo->mAmmoType) attachArrow(); else mAmmunition.reset(); @@ -197,7 +212,19 @@ osg::Group *CreatureWeaponAnimation::getArrowBone() if (!mWeapon) return nullptr; - SceneUtil::FindByNameVisitor findVisitor ("ArrowBone"); + if (!mPtr.getClass().hasInventoryStore(mPtr)) + return nullptr; + + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) + return nullptr; + + int type = weapon->get()->mBase->mData.mType; + int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; + + SceneUtil::FindByNameVisitor findVisitor (MWMechanics::getWeaponType(ammoType)->mAttachBone); + mWeapon->getNode()->accept(findVisitor); return findVisitor.mFoundNode; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 86fc54f68..09c2c8e1c 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -31,6 +31,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/weapontype.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -275,7 +276,7 @@ static NpcAnimation::PartBoneMap createPartListMap() result.insert(std::make_pair(ESM::PRT_LLeg, "Left Upper Leg")); result.insert(std::make_pair(ESM::PRT_RPauldron, "Right Clavicle")); result.insert(std::make_pair(ESM::PRT_LPauldron, "Left Clavicle")); - result.insert(std::make_pair(ESM::PRT_Weapon, "Weapon Bone")); + result.insert(std::make_pair(ESM::PRT_Weapon, "Weapon Bone")); // Fallback. The real node name depends on the current weapon type. result.insert(std::make_pair(ESM::PRT_Tail, "Tail")); return result; } @@ -318,12 +319,6 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) if(mViewMode == viewMode) return; - // Disable weapon sheathing in the 1st-person mode - if (viewMode == VM_FirstPerson) - mWeaponSheathing = false; - else - mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); - mViewMode = viewMode; MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale()); // apply race height after view change @@ -485,9 +480,6 @@ void NpcAnimation::updateNpcBase() setObjectRoot(smodel, true, true, false); - if (mWeaponSheathing) - injectWeaponBones(); - updateParts(); if(!is1stPerson) @@ -745,7 +737,26 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g mPartPriorities[type] = priority; try { - const std::string& bonename = sPartList.at(type); + std::string bonename = sPartList.at(type); + if (type == ESM::PRT_Weapon) + { + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()) + { + int weaponType = weapon->get()->mBase->mData.mType; + const std::string weaponBonename = MWMechanics::getWeaponType(weaponType)->mAttachBone; + + if (weaponBonename != bonename) + { + const NodeMap& nodeMap = getNodeMap(); + NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(weaponBonename)); + if (found != nodeMap.end()) + bonename = weaponBonename; + } + } + } + // PRT_Hair seems to be the only type that breaks consistency and uses a filter that's different from the attachment bone const std::string bonefilter = (type == ESM::PRT_Hair) ? "hair" : bonename; mObjectParts[type] = insertBoundedPart(mesh, bonename, bonefilter, enchantedGlow, glowColor); @@ -906,8 +917,9 @@ void NpcAnimation::showWeapons(bool showWeapon) if (weapon->getTypeName() == typeid(ESM::Weapon).name() && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { + int ammotype = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow)->mAmmoType; MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) + if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ammotype) attachArrow(); } } @@ -972,7 +984,15 @@ osg::Group* NpcAnimation::getArrowBone() if (!part) return nullptr; - SceneUtil::FindByNameVisitor findVisitor ("ArrowBone"); + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) + return nullptr; + + int type = weapon->get()->mBase->mData.mType; + int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; + + SceneUtil::FindByNameVisitor findVisitor (MWMechanics::getWeaponType(ammoType)->mAttachBone); part->getNode()->accept(findVisitor); return findVisitor.mFoundNode; diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 1b51f0462..24c92bc32 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -15,6 +15,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/combat.hpp" +#include "../mwmechanics/weapontype.hpp" #include "animation.hpp" #include "rotatecontroller.hpp" @@ -67,8 +68,10 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor) return; if (weaponSlot->getTypeName() != typeid(ESM::Weapon).name()) return; - int weaponType = weaponSlot->get()->mBase->mData.mType; - if (weaponType == ESM::Weapon::MarksmanThrown) + + int type = weaponSlot->get()->mBase->mData.mType; + ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass; + if (weapclass == ESM::WeaponType::Thrown) { std::string soundid = weaponSlot->getClass().getUpSoundId(*weaponSlot); if(!soundid.empty()) @@ -78,7 +81,7 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor) } showWeapon(true); } - else if (weaponType == ESM::Weapon::MarksmanBow || weaponType == ESM::Weapon::MarksmanCrossbow) + else if (weapclass == ESM::WeaponType::Ranged) { osg::Group* parent = getArrowBone(); if (!parent) @@ -113,7 +116,7 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) MWMechanics::applyFatigueLoss(actor, *weapon, attackStrength); - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + if (MWMechanics::getWeaponType(weapon->get()->mBase->mData.mType)->mWeaponClass == ESM::WeaponType::Thrown) { // Thrown weapons get detached now osg::Node* weaponNode = getWeaponNode(); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index f37354cba..65b1186aa 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -16,7 +16,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/actorutil.hpp" - +#include "../mwmechanics/weapontype.hpp" #include "esmstore.hpp" #include "class.hpp" @@ -332,7 +332,7 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots const ESM::Weapon* esmWeapon = iter->get()->mBase; - if (esmWeapon->mData.mType == ESM::Weapon::Arrow || esmWeapon->mData.mType == ESM::Weapon::Bolt) + if (MWMechanics::getWeaponType(esmWeapon->mData.mType)->mWeaponClass == ESM::WeaponType::Ammo) continue; if (iter->getClass().getEquipmentSkill(*iter) == weaponSkills[maxWeaponSkill]) @@ -357,31 +357,21 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots } } - bool isBow = false; - bool isCrossbow = false; - if (weapon != end()) - { - const MWWorld::LiveCellRef *ref = weapon->get(); - ESM::Weapon::Type type = (ESM::Weapon::Type)ref->mBase->mData.mType; - - if (type == ESM::Weapon::MarksmanBow) - isBow = true; - else if (type == ESM::Weapon::MarksmanCrossbow) - isCrossbow = true; - } - if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, actor).first) { // Do not equip ranged weapons, if there is no suitable ammo bool hasAmmo = true; - if (isBow == true) + const MWWorld::LiveCellRef *ref = weapon->get(); + int type = ref->mBase->mData.mType; + int ammotype = MWMechanics::getWeaponType(type)->mAmmoType; + if (ammotype == ESM::Weapon::Arrow) { if (arrow == end()) hasAmmo = false; else slots_[Slot_Ammunition] = arrow; } - if (isCrossbow == true) + else if (ammotype == ESM::Weapon::Bolt) { if (bolt == end()) hasAmmo = false; @@ -406,7 +396,7 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots int slot = itemsSlots.first.front(); slots_[slot] = weapon; - if (!isBow && !isCrossbow) + if (ammotype == ESM::Weapon::None) slots_[Slot_Ammunition] = end(); } diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index e7a3e8af1..2da8140f2 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -32,6 +32,7 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/aipackage.hpp" +#include "../mwmechanics/weapontype.hpp" #include "../mwrender/animation.hpp" #include "../mwrender/vismask.hpp" @@ -317,7 +318,9 @@ namespace MWWorld state.mIdArrow = projectile.getCellRef().getRefId(); state.mCasterHandle = actor; state.mAttackStrength = attackStrength; - state.mThrown = projectile.get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown; + + int type = projectile.get()->mBase->mData.mType; + state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId()); MWWorld::Ptr ptr = ref.getPtr(); @@ -604,7 +607,9 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); - state.mThrown = ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown; + + int weaponType = ptr.get()->mBase->mData.mType; + state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; } catch(...) { diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index c6ef401ed..c595e3ecb 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -388,6 +388,30 @@ namespace MWWorld iterator end() const; }; + template <> + class Store : public StoreBase + { + std::map mStatic; + + public: + typedef std::map::const_iterator iterator; + + Store(); + + const ESM::WeaponType *search(const int id) const; + const ESM::WeaponType *find(const int id) const; + + RecordId load(ESM::ESMReader &esm) { return RecordId(0, false); } + + ESM::WeaponType* insert(const ESM::WeaponType &weaponType); + + void setUp(); + + size_t getSize() const; + iterator begin() const; + iterator end() const; + }; + } //end namespace diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index dfe2b695a..90431756d 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -3,6 +3,8 @@ #include +#include "loadskil.hpp" + namespace ESM { @@ -21,6 +23,10 @@ struct Weapon enum Type { + PickProbe = -4, + HandToHand = -3, + Spell = -2, + None = -1, ShortBladeOneHand = 0, LongBladeOneHand = 1, LongBladeTwoHand = 2, @@ -75,5 +81,34 @@ struct Weapon void blank(); ///< Set record to default state (does not touch the ID). }; + +struct WeaponType +{ + enum Flags + { + TwoHanded = 0x01, + HasHealth = 0x02 + }; + + enum Class + { + Melee = 0, + Ranged = 1, + Thrown = 2, + Ammo = 3 + }; + + //std::string mDisplayName; // TODO: will be needed later for editor + std::string mShortGroup; + std::string mLongGroup; + std::string mSoundId; + std::string mAttachBone; + std::string mSheathingBone; + ESM::Skill::SkillEnum mSkill; + Class mWeaponClass; + int mAmmoType; + int mFlags; +}; + } #endif diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 60644c758..ac7d883d5 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -541,6 +541,10 @@ namespace NifOsg // Marker objects. These meshes are only visible in the editor. hasMarkers = true; } + else if(sd->string == "BONE") + { + node->getOrCreateUserDataContainer()->addDescription("CustomBone"); + } } }