From 216ebac2e901633855edf50a67f372accc1bd398 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 2 Aug 2014 23:14:17 +0200 Subject: [PATCH] Implement weapon condition damage for ranged weapons (Fixes #1746) --- apps/openmw/mwclass/creature.cpp | 43 +++---------------- apps/openmw/mwclass/npc.cpp | 42 ++----------------- apps/openmw/mwmechanics/combat.cpp | 51 ++++++++++++++++++++--- apps/openmw/mwmechanics/combat.hpp | 10 +++++ apps/openmw/mwworld/projectilemanager.cpp | 30 ++++++------- 5 files changed, 76 insertions(+), 100 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index e37fb477b..a57b47de4 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -255,23 +255,7 @@ namespace MWClass if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) { victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false); - - // Weapon health is reduced by 1 even if the attack misses - const bool weaphashealth = !weapon.isEmpty() && weapon.getClass().hasItemHealth(weapon); - if(weaphashealth) - { - int weaphealth = weapon.getClass().getItemHealth(weapon); - - if (!MWBase::Environment::get().getWorld()->getGodModeState()) - { - weaphealth -= std::min(1, weaphealth); - weapon.getCellRef().setCharge(weaphealth); - } - - // Weapon broken? unequip it - if (weapon.getCellRef().getCharge() == 0) - weapon = *getInventoryStore(ptr).unequipItem(weapon, ptr); - } + MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); return; } @@ -298,7 +282,6 @@ namespace MWClass if (!weapon.isEmpty()) { - const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); const unsigned char *attack = NULL; if(type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; @@ -309,26 +292,10 @@ namespace MWClass if(attack) { damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); - damage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f); - if(weaphashealth) - { - int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); - int weaphealth = weapon.getClass().getItemHealth(weapon); - damage *= float(weaphealth) / weapmaxhealth; - - if (!MWBase::Environment::get().getWorld()->getGodModeState()) - { - // Reduce weapon charge by at least one, but cap at 0 - weaphealth -= std::min(std::max(1, - (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weaphealth); - - weapon.getCellRef().setCharge(weaphealth); - } - - // Weapon broken? unequip it - if (weapon.getCellRef().getCharge() == 0) - weapon = *getInventoryStore(ptr).unequipItem(weapon, ptr); - } + damage *= gmst.find("fDamageStrengthBase")->getFloat() + + (stats.getAttribute(ESM::Attribute::Strength).getModified() * gmst.find("fDamageStrengthMult")->getFloat() * 0.1); + MWMechanics::adjustWeaponDamage(damage, weapon); + MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); } // Apply "On hit" enchanted weapons diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c37bc60e7..f8e5b14cc 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -529,24 +529,7 @@ namespace MWClass if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) { othercls.onHit(victim, 0.0f, false, weapon, ptr, false); - - // Weapon health is reduced by 1 even if the attack misses - const bool weaphashealth = !weapon.isEmpty() && weapon.getClass().hasItemHealth(weapon); - if(weaphashealth) - { - int weaphealth = weapon.getClass().getItemHealth(weapon); - - if (!MWBase::Environment::get().getWorld()->getGodModeState()) - { - weaphealth -= std::min(1, weaphealth); - weapon.getCellRef().setCharge(weaphealth); - } - - // Weapon broken? unequip it - if (weaphealth == 0) - weapon = *inv.unequipItem(weapon, ptr); - } - + MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); return; } @@ -555,7 +538,6 @@ namespace MWClass MWMechanics::NpcStats &stats = getNpcStats(ptr); if(!weapon.isEmpty()) { - const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); const unsigned char *attack = NULL; if(type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; @@ -568,27 +550,9 @@ namespace MWClass damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); damage *= gmst.fDamageStrengthBase->getFloat() + (stats.getAttribute(ESM::Attribute::Strength).getModified() * gmst.fDamageStrengthMult->getFloat() * 0.1); - if(weaphashealth) - { - int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); - int weaphealth = weapon.getClass().getItemHealth(weapon); - - damage *= float(weaphealth) / weapmaxhealth; - - if (!MWBase::Environment::get().getWorld()->getGodModeState()) - { - // Reduce weapon charge by at least one, but cap at 0 - weaphealth -= std::min(std::max(1, - (int)(damage * store.find("fWeaponDamageMult")->getFloat())), weaphealth); - - weapon.getCellRef().setCharge(weaphealth); - } - - // Weapon broken? unequip it - if (weaphealth == 0) - weapon = *inv.unequipItem(weapon, ptr); - } } + MWMechanics::adjustWeaponDamage(damage, weapon); + MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); healthdmg = true; } else diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index c13eac98c..c04e2c62d 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -169,12 +169,12 @@ namespace MWMechanics MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); - const MWWorld::Class &othercls = victim.getClass(); - if(!othercls.isActor()) // Can't hit non-actors - return; - MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim); - if(otherstats.isDead()) // Can't hit dead actors + if(victim.isEmpty() || !victim.getClass().isActor() || victim.getClass().getCreatureStats(victim).isDead()) + // Can't hit non-actors or dead actors + { + reduceWeaponCondition(0.f, false, weapon, attacker); return; + } if(attacker.getRefData().getHandle() == "player") MWBase::Environment::get().getWindowManager()->setEnemy(victim); @@ -189,6 +189,7 @@ namespace MWMechanics if((::rand()/(RAND_MAX+1.0)) > getHitChance(attacker, victim, skillValue)/100.0f) { victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); + MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker); return; } @@ -209,6 +210,8 @@ namespace MWMechanics damage *= fDamageStrengthBase + (attackerStats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1); + adjustWeaponDamage(damage, weapon); + reduceWeaponCondition(damage, true, weapon, attacker); if(attacker.getRefData().getHandle() == "player") attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0); @@ -295,4 +298,42 @@ namespace MWMechanics } } + void reduceWeaponCondition(float damage, bool hit, MWWorld::Ptr &weapon, const MWWorld::Ptr &attacker) + { + if (weapon.isEmpty()) + return; + + if (!hit) + damage = 0.f; + + const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); + if(weaphashealth) + { + int weaphealth = weapon.getClass().getItemHealth(weapon); + + const float fWeaponDamageMult = MWBase::Environment::get().getWorld()->getStore().get().find("fWeaponDamageMult")->getFloat(); + float x = std::max(1.f, fWeaponDamageMult * damage); + + weaphealth -= std::min(int(x), weaphealth); + weapon.getCellRef().setCharge(weaphealth); + + // Weapon broken? unequip it + if (weaphealth == 0) + weapon = *attacker.getClass().getInventoryStore(attacker).unequipItem(weapon, attacker); + } + } + + void adjustWeaponDamage(float &damage, const MWWorld::Ptr &weapon) + { + if (weapon.isEmpty()) + return; + + const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); + if(weaphashealth) + { + int weaphealth = weapon.getClass().getItemHealth(weapon); + int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); + damage *= (float(weaphealth) / weapmaxhealth); + } + } } diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index eb89cb820..0d3009510 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -13,6 +13,7 @@ bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker void resistNormalWeapon (const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage); /// @note for a thrown weapon, \a weapon == \a projectile, for bows/crossbows, \a projectile is the arrow/bolt +/// @note \a victim may be empty (e.g. for a hit on terrain), a non-actor (environment objects) or an actor void projectileHit (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile, const Ogre::Vector3& hitPosition); @@ -22,6 +23,15 @@ float getHitChance (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, in /// Applies damage to attacker based on the victim's elemental shields. void applyElementalShields(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim); +/// @param damage Unmitigated weapon damage of the attack +/// @param hit Was the attack successful? +/// @param weapon The weapon used. +/// @note if the weapon is unequipped as result of condition damage, a new Ptr will be assigned to \a weapon. +void reduceWeaponCondition (float damage, bool hit, MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker); + +/// Adjust weapon damage based on its condition. A used weapon will be less effective. +void adjustWeaponDamage (float& damage, const MWWorld::Ptr& weapon); + } #endif diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index fb376bb93..ff1743c64 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -247,29 +247,23 @@ namespace MWWorld if (obstacle == caster) continue; - if (obstacle.isEmpty()) + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mId); + + // Try to get a Ptr to the bow that was used. It might no longer exist. + MWWorld::Ptr bow = projectileRef.getPtr(); + if (!caster.isEmpty()) { - // Terrain + MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); + MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) + bow = *invIt; } - else if (obstacle.getClass().isActor()) - { - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mId); - // Try to get a Ptr to the bow that was used. It might no longer exist. - MWWorld::Ptr bow = projectileRef.getPtr(); - if (!caster.isEmpty()) - { - MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); - MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) - bow = *invIt; - } + if (caster.isEmpty()) + caster = obstacle; - if (caster.isEmpty()) - caster = obstacle; + MWMechanics::projectileHit(caster, obstacle, bow, projectileRef.getPtr(), pos + (newPos - pos) * cIt->first); - MWMechanics::projectileHit(caster, obstacle, bow, projectileRef.getPtr(), pos + (newPos - pos) * cIt->first); - } hit = true; } if (hit)