From b3a057d679a01d2189108f527fa8e9e67405cc2b Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 25 Jul 2013 08:15:42 -0700 Subject: [PATCH] Handle hit chance and damage calculation Math is based on what the UESP describes, with some tweaks (using fatigue term, and the fCombatCriticalStrikeMult GMST): http://www.uesp.net/wiki/Morrowind:Combat --- apps/openmw/mwclass/npc.cpp | 69 +++++++++++++++++++++++++---------- apps/openmw/mwworld/class.cpp | 14 +++++++ apps/openmw/mwworld/class.hpp | 3 ++ 3 files changed, 67 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index e2fdb037b..5cc09624b 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -14,6 +14,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" @@ -304,6 +305,12 @@ namespace MWClass void Npc::hit(const MWWorld::Ptr& ptr, int type) const { + // Get the weapon used (if hand-to-hand, weapon = inv.end()) + MWWorld::InventoryStore &inv = getInventoryStore(ptr); + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon != inv.end() && weapon->getTypeName() != typeid(ESM::Weapon).name()) + weapon = inv.end(); + // FIXME: Detect what was hit MWWorld::Ptr victim; if(victim.isEmpty()) // Didn't hit anything @@ -316,34 +323,58 @@ namespace MWClass return; } - // Get the weapon used - MWWorld::LiveCellRef *weapon = NULL; - MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr); - MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(iter != inv.end() && iter->getTypeName() == typeid(ESM::Weapon).name()) - weapon = iter->get(); + int weapskill = ESM::Skill::HandToHand; + if(weapon != inv.end()) + weapskill = MWWorld::Class::get(*weapon).getEquipmentSkill(*weapon); + + MWMechanics::CreatureStats &crstats = getCreatureStats(ptr); + MWMechanics::NpcStats &npcstats = getNpcStats(ptr); + const MWMechanics::MagicEffects &mageffects = crstats.getMagicEffects(); + float hitchance = npcstats.getSkill(weapskill).getModified() + + (crstats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + + (crstats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); + hitchance *= crstats.getFatigueTerm(); + hitchance += mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::FortifyAttack)).mMagnitude - + mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::Blind)).mMagnitude; + hitchance -= othercls.getEvasion(victim); + + if((::rand()/(RAND_MAX+1.0)) > hitchance) + { + // Missed + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(victim, "miss", 1.0f, 1.0f); + return; + } - // TODO: Check weapon skill against victim's armor skill (if !weapon, attacker is using - // hand-to-hand, which damages fatique unless in werewolf form). - if(weapon) + if(weapon != inv.end()) { - const unsigned char *att = NULL; + const unsigned char *attack = NULL; if(type == MWMechanics::CreatureStats::AT_Chop) - att = weapon->mBase->mData.mChop; + attack = weapon->get()->mBase->mData.mChop; else if(type == MWMechanics::CreatureStats::AT_Slash) - att = weapon->mBase->mData.mSlash; + attack = weapon->get()->mBase->mData.mSlash; else if(type == MWMechanics::CreatureStats::AT_Thrust) - att = weapon->mBase->mData.mThrust; - - if(att) + attack = weapon->get()->mBase->mData.mThrust; + if(attack) { - float health = othercls.getCreatureStats(victim).getHealth().getCurrent(); - // FIXME: Modify damage based on strength attribute? - health -= att[0] + ((att[1]-att[0])*Npc::getNpcStats(ptr).getAttackStrength()); + float damage = attack[0] + ((attack[1]-attack[0])*npcstats.getAttackStrength()); + damage *= 0.5f + (crstats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f); + //damage *= weapon_current_health / weapon_max_health; + if(!othercls.hasDetected(victim, ptr)) + { + const MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Store &gmst = world->getStore().get(); + damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat(); + MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); + } + damage /= std::min(1.0f + othercls.getArmorRating(victim) / std::max(1.0f, damage), 4.0f); - othercls.setActorHealth(victim, std::max(health, 0.0f), ptr); + float health = othercls.getCreatureStats(victim).getHealth().getCurrent() - damage; + othercls.setActorHealth(victim, health, ptr); } } + + skillUsageSucceeded(ptr, weapskill, 0); } void Npc::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index c30fb51c1..8a2b87b80 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -13,6 +13,8 @@ #include "containerstore.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/magiceffects.hpp" namespace MWWorld { @@ -77,6 +79,18 @@ namespace MWWorld throw std::runtime_error ("class does not have item health"); } + float Class::getEvasion(const Ptr& ptr) const + { + MWMechanics::CreatureStats &crstats = getCreatureStats(ptr); + const MWMechanics::MagicEffects &mageffects = crstats.getMagicEffects(); + float evasion = (crstats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + + (crstats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); + evasion *= crstats.getFatigueTerm(); + evasion += mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::Sanctuary)).mMagnitude; + + return evasion; + } + void Class::hit(const Ptr& ptr, int type) const { throw std::runtime_error("class cannot hit"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 2f5ac3ec8..89d914746 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -105,6 +105,9 @@ namespace MWWorld ///< Return item max health or throw an exception, if class does not have item health /// (default implementation: throw an exceoption) + virtual float getEvasion(const Ptr& ptr) const; + ///< Gets the chance the given object can evade an attack + virtual void hit(const Ptr& ptr, int type=-1) const; ///< Execute a melee hit, using the current weapon. This will check the relevant skills /// of the given attacker, and whoever is hit.