diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index f5bd434b1..5a5ae6f41 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -148,6 +148,39 @@ namespace MWClass return dynamic_cast (*ptr.getRefData().getCustomData()).mCreatureStats; } + + void Creature::attack(const MWWorld::Ptr& ptr, int type) const + { + } + + void Creature::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const + { + MWMechanics::CreatureStats &crstats = getCreatureStats(ptr); + float diff = health - crstats.getHealth().getCurrent(); + + if(diff < 0.0f) + { + // actor is losing health. Alert the character controller, scripts, etc. + // NOTE: 'attacker' may be empty. + } + + bool wasDead = crstats.isDead(); + + MWMechanics::DynamicStat stat(crstats.getHealth()); + stat.setCurrent(health); + crstats.setHealth(stat); + + if(!wasDead && crstats.isDead()) + { + // actor was just killed + } + else if(wasDead && !crstats.isDead()) + { + // actor was just resurrected + } + } + + boost::shared_ptr Creature::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 356ecfb64..b90b07f3f 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -42,6 +42,10 @@ namespace MWClass virtual MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const; ///< Return creature stats + virtual void attack(const MWWorld::Ptr& ptr, int type) const; + + virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const; + virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; ///< Generate action for activation diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b6af9f0e5..0ba633df2 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -288,6 +288,74 @@ namespace MWClass return dynamic_cast (*ptr.getRefData().getCustomData()).mNpcStats; } + + void Npc::attack(const MWWorld::Ptr& ptr, int type) const + { + // FIXME: Detect what was hit + MWWorld::Ptr victim; + if(victim.isEmpty()) // Didn't hit anything + return; + + const MWWorld::Class &othercls = MWWorld::Class::get(victim); + if(!othercls.isActor() || othercls.getCreatureStats(victim).isDead()) + { + // Can't hit non-actors, or dead actors + 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(); + + // 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) + { + float health = othercls.getCreatureStats(victim).getHealth().getCurrent(); + // FIXME: Modify damage based on strength? + if(type == MWMechanics::CreatureStats::AT_Chop) + health -= weapon->mBase->mData.mChop[1]; + else if(type == MWMechanics::CreatureStats::AT_Slash) + health -= weapon->mBase->mData.mSlash[1]; + else if(type == MWMechanics::CreatureStats::AT_Thrust) + health -= weapon->mBase->mData.mThrust[1]; + + othercls.setActorHealth(victim, std::max(health, 0.0f), ptr); + } + } + + void Npc::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const + { + MWMechanics::CreatureStats &crstats = getCreatureStats(ptr); + float diff = health - crstats.getHealth().getCurrent(); + + if(diff < 0.0f) + { + // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying + // something, alert the character controller, scripts, etc. + // NOTE: 'attacker' may be empty. + } + + bool wasDead = crstats.isDead(); + + MWMechanics::DynamicStat stat(crstats.getHealth()); + stat.setCurrent(health); + crstats.setHealth(stat); + + if(!wasDead && crstats.isDead()) + { + // actor was just killed + } + else if(wasDead && !crstats.isDead()) + { + // actor was just resurrected + } + } + + boost::shared_ptr Npc::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 2a73df593..15f715f14 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -68,6 +68,10 @@ namespace MWClass virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const; ///< Return inventory store + virtual void attack(const MWWorld::Ptr& ptr, int type) const; + + virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const; + virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; ///< Generate action for activation diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index db20807e3..07c1c1fe7 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -14,6 +14,7 @@ #include "../mwbase/world.hpp" #include "../mwmechanics/character.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "../mwworld/class.hpp" namespace MWRender @@ -548,6 +549,14 @@ bool Animation::handleTextKey(AnimState &state, const std::string &groupname, co showWeapons(true); else if(evt.compare(off, len, "unequip detach") == 0) showWeapons(false); + else if(evt.compare(off, len, "chop hit") == 0) + MWWorld::Class::get(mPtr).attack(mPtr, MWMechanics::CreatureStats::AT_Chop); + else if(evt.compare(off, len, "slash hit") == 0) + MWWorld::Class::get(mPtr).attack(mPtr, MWMechanics::CreatureStats::AT_Slash); + else if(evt.compare(off, len, "thrust hit") == 0) + MWWorld::Class::get(mPtr).attack(mPtr, MWMechanics::CreatureStats::AT_Thrust); + else if(evt.compare(off, len, "hit") == 0) + MWWorld::Class::get(mPtr).attack(mPtr, -1); return true; } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 6f420942b..b64b5119a 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -77,6 +77,16 @@ namespace MWWorld throw std::runtime_error ("class does not have item health"); } + void Class::attack(const Ptr& ptr, int type) const + { + throw std::runtime_error("class cannot attack"); + } + + void Class::setActorHealth(const Ptr& ptr, float health, const Ptr& attacker) const + { + throw std::runtime_error("class does not have actor health"); + } + boost::shared_ptr Class::activate (const Ptr& ptr, const Ptr& actor) const { return boost::shared_ptr (new NullAction); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index eed100024..26ce65d0e 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -105,6 +105,19 @@ namespace MWWorld ///< Return item max health or throw an exception, if class does not have item health /// (default implementation: throw an exceoption) + virtual void attack(const Ptr& ptr, int type) const; + ///< Execute a melee hit, using the current weapon. This will check the relevant skills + /// of the given attacker, and whoever is hit. + /// \a type - type of attack, one of the MWMechanics::CreatureStats::AttackType enums. + /// ignored for creature attacks. + /// (default implementation: throw an exceoption) + + virtual void setActorHealth(const Ptr& ptr, float health, const Ptr& attacker=Ptr()) const; + ///< Sets a new current health value for the actor, optionally specifying the object causing + /// the change. Use this instead of using CreatureStats directly as this will make sure the + /// correct dialog and actor states are properly handled when being hurt or healed. + /// (default implementation: throw an exceoption) + virtual boost::shared_ptr activate (const Ptr& ptr, const Ptr& actor) const; ///< Generate action for activation (default implementation: return a null action).