diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 6df6210ee1..a7e263b11a 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -77,7 +77,7 @@ add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting - disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling + disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction ) add_openmw_dir (mwstate diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index d59b0a3ec2..94f5574bdb 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -22,6 +22,8 @@ #include "movement.hpp" #include "character.hpp" // fixme: for getActiveWeapon +#include "aicombataction.hpp" + namespace { static float sgn(Ogre::Radian a) @@ -107,6 +109,7 @@ namespace MWMechanics void AiCombat::init() { + mActionCooldown = 0; mTimerAttack = 0; mTimerReact = 0; mTimerCombatMove = 0; @@ -246,6 +249,8 @@ namespace MWMechanics actorClass.getCreatureStats(actor).setAttackingOrSpell(mAttack); + mActionCooldown -= duration; + float tReaction = 0.25f; if(mTimerReact < tReaction) { @@ -263,19 +268,33 @@ namespace MWMechanics mCell = actor.getCell(); } + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(actor); + if (!anim) // shouldn't happen + return false; + + actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); + + if (mActionCooldown > 0) + return false; + + float rangeAttack = 0; + float rangeFollow = 0; + if (anim->upperBodyReady()) + { + mCurrentAction = prepareNextAction(actor, target); + mActionCooldown = mCurrentAction->getActionCooldown(); + } + if (mCurrentAction.get()) + mCurrentAction->getCombatRange(rangeAttack, rangeFollow); + + // FIXME: consider moving this stuff to ActionWeapon::getCombatRange const ESM::Weapon *weapon = NULL; MWMechanics::WeaponType weaptype; float weapRange = 1.0f; - actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); - // Get weapon characteristics if (actorClass.hasInventoryStore(actor)) { - MWMechanics::DrawState_ state = actorClass.getCreatureStats(actor).getDrawState(); - if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - actorClass.getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon); - // TODO: Check equipped weapon and equip a different one if we can't attack with it // (e.g. no ammunition, or wrong type of ammunition equipped, etc. autoEquip is not very smart in this regard)) @@ -285,7 +304,7 @@ namespace MWMechanics if (weaptype == WeapType_HandToHand) { - static float fHandToHandReach = + static float fHandToHandReach = world->getStore().get().find("fHandToHandReach")->getFloat(); weapRange = fHandToHandReach; } @@ -303,19 +322,26 @@ namespace MWMechanics weapRange = 150.0f; //TODO: use true attack range (the same problem in Creature::hit) } - float rangeAttack; - float rangeFollow; bool distantCombat = false; - if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) + if (weaptype != WeapType_Spell) { - rangeAttack = 1000; // TODO: should depend on archer skill - rangeFollow = 0; // not needed in ranged combat - distantCombat = true; + // TODO: move to ActionWeapon + if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) + { + rangeAttack = 1000; + rangeFollow = 0; // not needed in ranged combat + distantCombat = true; + } + else + { + rangeAttack = weapRange; + rangeFollow = 300; + } } else { - rangeAttack = weapRange; - rangeFollow = 300; + distantCombat = (rangeAttack > 500); + weapRange = 150.f; } // start new attack @@ -345,7 +371,7 @@ namespace MWMechanics MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); } } - } + } } @@ -750,7 +776,8 @@ void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations MWMechanics::getActiveWeapon(actor.getClass().getCreatureStats(actor), actor.getClass().getInventoryStore(actor), &weaptype); float weapSpeed; - if (weaptype != MWMechanics::WeapType_HandToHand) + if (weaptype != MWMechanics::WeapType_HandToHand + && weaptype != MWMechanics::WeapType_Spell) { weapon = weaponSlot->get()->mBase; weapSpeed = weapon->mData.mSpeed; diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 311dee6179..916a1a1d5e 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -14,6 +14,8 @@ #include "../mwbase/world.hpp" +#include + namespace ESM { namespace AiSequence @@ -24,6 +26,8 @@ namespace ESM namespace MWMechanics { + class Action; + /// \brief Causes the actor to fight another actor class AiCombat : public AiPackage { @@ -79,6 +83,9 @@ namespace MWMechanics const MWWorld::CellStore* mCell; ObstacleCheck mObstacleCheck; + boost::shared_ptr mCurrentAction; + float mActionCooldown; + void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); }; } diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp new file mode 100644 index 0000000000..7a8ee75fc9 --- /dev/null +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -0,0 +1,397 @@ +#include "aicombataction.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/actionequip.hpp" + +#include "../mwmechanics/creaturestats.hpp" + +#include +#include + +namespace +{ + +// RangeTypes using bitflags to allow multiple range types, as can be the case with spells having multiple effects. +enum RangeTypes +{ + Self = 0x1, + Touch = 0x10, + Target = 0x100 +}; + +int getRangeTypes (const ESM::EffectList& effects) +{ + int types = 0; + for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) + { + if (it->mRange == ESM::RT_Self) + types |= Self; + else if (it->mRange == ESM::RT_Touch) + types |= Touch; + else if (it->mRange == ESM::RT_Target) + types |= Target; + } + return types; +} + +void suggestCombatRange(int rangeTypes, float& rangeAttack, float& rangeFollow) +{ + if (rangeTypes & Touch) + { + rangeAttack = 100.f; + rangeFollow = 300.f; + } + else if (rangeTypes & Target) + { + rangeAttack = 1000.f; + rangeFollow = 0.f; + } + else + { + // For Self spells, distance doesn't matter, so back away slightly to avoid enemy hits + rangeAttack = 600.f; + rangeFollow = 0.f; + } +} + +} + +namespace MWMechanics +{ + + float ratePotion (const MWWorld::Ptr &item, const MWWorld::Ptr& actor) + { + if (item.getTypeName() != typeid(ESM::Potion).name()) + return 0.f; + + const ESM::Potion* potion = item.get()->mBase; + return rateEffects(potion->mEffects, actor, MWWorld::Ptr()); + } + + float rateWeapon (const MWWorld::Ptr &item, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) + { + if (item.getTypeName() != typeid(ESM::Weapon).name()) + return 0.f; + + const ESM::Weapon* weapon = item.get()->mBase; + + // TODO: Check that we have ammunition if needed + + float rating=0.f; + + if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) + { + rating = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f; + } + else + { + for (int i=0; i<2; ++i) + { + rating += weapon->mData.mSlash[i]; + rating += weapon->mData.mThrust[i]; + rating += weapon->mData.mChop[i]; + } + rating /= 6.f; + } + + if (item.getClass().hasItemHealth(item)) + rating *= item.getClass().getItemHealth(item) / float(item.getClass().getItemMaxHealth(item)); + + rating *= actor.getClass().getSkill(actor, item.getClass().getEquipmentSkill(item)) / 100.f; + + if (!weapon->mEnchant.empty()) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(weapon->mEnchant); + if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes + && (item.getCellRef().getEnchantmentCharge() == -1 + || item.getCellRef().getEnchantmentCharge() >= enchantment->mData.mCost)) + rating += rateEffects(enchantment->mEffects, actor, target); + } + return rating; + } + + float rateSpell(const ESM::Spell *spell, const MWWorld::Ptr &actor, const MWWorld::Ptr& target) + { + const CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + // Never casting racial spells (ST_Power and F_Always) + if (spell->mData.mType != ESM::Spell::ST_Spell || spell->mData.mFlags & ESM::Spell::F_Always) + return 0.f; + + if (spell->mData.mCost > stats.getMagicka().getCurrent()) + return 0.f; + + // Spells don't stack, so early out if the spell is still active on the target + int types = getRangeTypes(spell->mEffects); + if ((types & Self) && stats.getActiveSpells().isSpellActive(spell->mId)) + return 0.f; + if ( ((types & Touch) || (types & Target)) && target.getClass().getCreatureStats(target).getActiveSpells().isSpellActive(spell->mId)) + return 0.f; + + return rateEffects(spell->mEffects, actor, target); + } + + float rateMagicItem(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor, const MWWorld::Ptr& target) + { + if (ptr.getClass().getEnchantment(ptr).empty()) + return 0.f; + + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(ptr.getClass().getEnchantment(ptr)); + if (enchantment->mData.mType == ESM::Enchantment::CastOnce) + { + return rateEffects(enchantment->mEffects, actor, target); + } + else + return 0.f; + } + + float rateEffect(const ESM::ENAMstruct &effect, const MWWorld::Ptr &actor, const MWWorld::Ptr &target) + { + // NOTE: target may be empty + + float baseRating = 1; + switch (effect.mEffectID) + { + case ESM::MagicEffect::Soultrap: + case ESM::MagicEffect::AlmsiviIntervention: + case ESM::MagicEffect::DivineIntervention: + case ESM::MagicEffect::CalmCreature: + case ESM::MagicEffect::CalmHumanoid: + case ESM::MagicEffect::Charm: + case ESM::MagicEffect::DetectAnimal: + case ESM::MagicEffect::DetectEnchantment: + case ESM::MagicEffect::DetectKey: + case ESM::MagicEffect::FrenzyCreature: + case ESM::MagicEffect::FrenzyHumanoid: + case ESM::MagicEffect::Telekinesis: + case ESM::MagicEffect::Mark: + case ESM::MagicEffect::Recall: + case ESM::MagicEffect::Jump: + case ESM::MagicEffect::WaterBreathing: + case ESM::MagicEffect::SwiftSwim: + case ESM::MagicEffect::WaterWalking: + case ESM::MagicEffect::SlowFall: + case ESM::MagicEffect::Light: + case ESM::MagicEffect::Lock: + case ESM::MagicEffect::Open: + case ESM::MagicEffect::TurnUndead: + return 0.f; + case ESM::MagicEffect::Feather: + return 0.f; // TODO: check if target is overencumbered + case ESM::MagicEffect::Levitate: + return 0.f; // AI isn't designed to take advantage of this, and could be perceived as unfair anyway + // TODO: check if Beast race (can't wear boots or helm) + /* + case ESM::MagicEffect::BoundBoots: + case ESM::MagicEffect::BoundHelm: + */ + case ESM::MagicEffect::RestoreHealth: + case ESM::MagicEffect::RestoreMagicka: + case ESM::MagicEffect::RestoreFatigue: + if (effect.mRange == ESM::RT_Self) + { + const DynamicStat& current = actor.getClass().getCreatureStats(actor). + getDynamic(effect.mEffectID - ESM::MagicEffect::RestoreHealth); + float toHeal = (effect.mMagnMin + effect.mMagnMax)/2.f * effect.mDuration; + // Effect doesn't heal more than we need, *or* we are below 1/2 health + if (current.getModified() - current.getCurrent() > toHeal + || current.getCurrent() < current.getModified()*0.5) + return 10000.f; + else + return -10000.f; // Save for later + } + break; + + // Give a small boost to all direct damage effects. This is combat, after all! + case ESM::MagicEffect::FireDamage: + case ESM::MagicEffect::ShockDamage: + case ESM::MagicEffect::FrostDamage: + case ESM::MagicEffect::Poison: + case ESM::MagicEffect::AbsorbHealth: + case ESM::MagicEffect::DamageHealth: + baseRating *= 4; + break; + + case ESM::MagicEffect::Paralyze: // *Evil laughter* + baseRating *= 5; + break; + + // TODO: rate these effects very high if we are currently suffering from negative effects that could be cured + case ESM::MagicEffect::Dispel: + case ESM::MagicEffect::CureParalyzation: + case ESM::MagicEffect::CurePoison: + break; + + default: + break; + } + + // TODO: for non-cumulative effects (e.g. paralyze), check if the target is already suffering from them + + // TODO: could take into account target's resistance/weakness against the effect + + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); + + baseRating *= magicEffect->mData.mBaseCost; + + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + return 0.f; // No clue how useful this would be; will need special cases for each effect + + float rating = baseRating * (effect.mMagnMin + effect.mMagnMax)/2.f; + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) + rating *= effect.mDuration; + + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) + rating *= -1.f; + + // Currently treating all "on target" or "on touch" effects to target the enemy actor. + // Combat AI is egoistic, so doesn't consider applying positive effects to friendly actors. + if (effect.mRange != ESM::RT_Self) + rating *= -1.f; + return rating; + } + + float rateEffects(const ESM::EffectList &list, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) + { + // NOTE: target may be empty + float rating = 0.f; + for (std::vector::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) + { + rating += rateEffect(*it, actor, target); + } + return rating; + } + + void ActionSpell::prepare(const MWWorld::Ptr &actor) + { + actor.getClass().getCreatureStats(actor).getSpells().setSelectedSpell(mSpellId); + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Spell); + if (actor.getClass().hasInventoryStore(actor)) + { + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + inv.setSelectedEnchantItem(inv.end()); + } + } + + void ActionSpell::getCombatRange(float& rangeAttack, float& rangeFollow) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); + int types = getRangeTypes(spell->mEffects); + suggestCombatRange(types, rangeAttack, rangeFollow); + } + + void ActionEnchantedItem::prepare(const MWWorld::Ptr &actor) + { + actor.getClass().getCreatureStats(actor).getSpells().setSelectedSpell(std::string()); + actor.getClass().getInventoryStore(actor).setSelectedEnchantItem(mItem); + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Spell); + } + + void ActionEnchantedItem::getCombatRange(float& rangeAttack, float& rangeFollow) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(mItem->getClass().getEnchantment(*mItem)); + int types = getRangeTypes(enchantment->mEffects); + suggestCombatRange(types, rangeAttack, rangeFollow); + } + + void ActionPotion::getCombatRange(float& rangeAttack, float& rangeFollow) + { + // distance doesn't matter, so back away slightly to avoid enemy hits + rangeAttack = 600.f; + rangeFollow = 0.f; + } + + void ActionPotion::prepare(const MWWorld::Ptr &actor) + { + actor.getClass().apply(actor, mPotion.getCellRef().getRefId(), actor); + actor.getClass().getContainerStore(actor).remove(mPotion, 1, actor); + } + + void ActionWeapon::prepare(const MWWorld::Ptr &actor) + { + if (actor.getClass().hasInventoryStore(actor)) + { + if (mWeapon.isEmpty()) + actor.getClass().getInventoryStore(actor).unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, actor); + else + { + MWWorld::ActionEquip equip(mWeapon); + equip.execute(actor); + } + // TODO: equip ammunition and shield where needed + } + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Weapon); + } + + void ActionWeapon::getCombatRange(float& rangeAttack, float& rangeFollow) + { + // Already done in AiCombat itself + } + + boost::shared_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &target) + { + Spells& spells = actor.getClass().getCreatureStats(actor).getSpells(); + + float bestActionRating = 0.f; + // Default to hand-to-hand combat + boost::shared_ptr bestAction (new ActionWeapon(MWWorld::Ptr())); + + if (actor.getClass().hasInventoryStore(actor)) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + float rating = ratePotion(*it, actor); + if (rating > bestActionRating) + { + bestActionRating = rating; + bestAction.reset(new ActionPotion(*it)); + } + } + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + float rating = rateMagicItem(*it, actor, target); + if (rating > bestActionRating) + { + bestActionRating = rating; + bestAction.reset(new ActionEnchantedItem(it)); + } + } + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + float rating = rateWeapon(*it, actor, target); + if (rating > bestActionRating) + { + bestActionRating = rating; + bestAction.reset(new ActionWeapon(*it)); + } + } + } + + for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); + + float rating = rateSpell(spell, actor, target); + if (rating > bestActionRating) + { + bestActionRating = rating; + bestAction.reset(new ActionSpell(spell->mId)); + } + } + + if (bestAction.get()) + bestAction->prepare(actor); + + return bestAction; + } + +} diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp new file mode 100644 index 0000000000..8c95e14c69 --- /dev/null +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -0,0 +1,83 @@ +#ifndef OPENMW_AICOMBAT_ACTION_H +#define OPENMW_AICOMBAT_ACTION_H + +#include + +#include "../mwworld/ptr.hpp" +#include "../mwworld/containerstore.hpp" + +#include + +namespace MWMechanics +{ + + class Action + { + public: + virtual void prepare(const MWWorld::Ptr& actor) = 0; + virtual void getCombatRange (float& rangeAttack, float& rangeFollow) = 0; + virtual float getActionCooldown() { return 0.f; } + }; + + class ActionSpell : public Action + { + public: + ActionSpell(const std::string& spellId) : mSpellId(spellId) {} + std::string mSpellId; + /// Sets the given spell as selected on the actor's spell list. + virtual void prepare(const MWWorld::Ptr& actor); + + virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + }; + + class ActionEnchantedItem : public Action + { + public: + ActionEnchantedItem(const MWWorld::ContainerStoreIterator& item) : mItem(item) {} + MWWorld::ContainerStoreIterator mItem; + /// Sets the given item as selected enchanted item in the actor's InventoryStore. + virtual void prepare(const MWWorld::Ptr& actor); + virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + + /// Since this action has no animation, apply a small cool down for using it + virtual float getActionCooldown() { return 1.f; } + }; + + class ActionPotion : public Action + { + public: + ActionPotion(const MWWorld::Ptr& potion) : mPotion(potion) {} + MWWorld::Ptr mPotion; + /// Drinks the given potion. + virtual void prepare(const MWWorld::Ptr& actor); + virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + + /// Since this action has no animation, apply a small cool down for using it + virtual float getActionCooldown() { return 1.f; } + }; + + class ActionWeapon : public Action + { + public: + /// \a weapon may be empty for hand-to-hand combat + ActionWeapon(const MWWorld::Ptr& weapon) : mWeapon(weapon) {} + MWWorld::Ptr mWeapon; + /// Equips the given weapon. + virtual void prepare(const MWWorld::Ptr& actor); + virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + }; + + float rateSpell (const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + float rateMagicItem (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Ptr &target); + float ratePotion (const MWWorld::Ptr& item, const MWWorld::Ptr &actor); + float rateWeapon (const MWWorld::Ptr& item, const MWWorld::Ptr& actor); + + /// @note target may be empty + float rateEffect (const ESM::ENAMstruct& effect, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + /// @note target may be empty + float rateEffects (const ESM::EffectList& list, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + + boost::shared_ptr prepareNextAction (const MWWorld::Ptr& actor, const MWWorld::Ptr& target); +} + +#endif diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index ab181ca77e..a8b453e890 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1198,7 +1198,7 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj) mSkelBase->detachObjectFromBone(obj); } -bool Animation::allowSwitchViewMode() const +bool Animation::upperBodyReady() const { for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter) { diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index b85e74549f..4f53a737b5 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -263,8 +263,8 @@ public: /** Returns true if the named animation group is playing. */ bool isPlaying(const std::string &groupname) const; - //Checks if playing any animation which shouldn't be stopped when switching camera view modes - bool allowSwitchViewMode() const; + /// Returns true if no important animations are currently playing on the upper body. + bool upperBodyReady() const; /** Gets info about the given animation group. * \param groupname Animation group to check. diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 4580bae70b..f38e00cce5 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -107,7 +107,7 @@ namespace MWRender void Camera::update(float duration, bool paused) { - if (mAnimation->allowSwitchViewMode()) + if (mAnimation->upperBodyReady()) { // Now process the view changes we queued earlier if (mVanityToggleQueued) @@ -144,7 +144,7 @@ namespace MWRender { // Changing the view will stop all playing animations, so if we are playing // anything important, queue the view change for later - if (!mAnimation->allowSwitchViewMode() && !force) + if (!mAnimation->upperBodyReady() && !force) { mViewModeToggleQueued = true; return; @@ -207,7 +207,7 @@ namespace MWRender void Camera::togglePreviewMode(bool enable) { - if (mFirstPersonView && !mAnimation->allowSwitchViewMode()) + if (mFirstPersonView && !mAnimation->upperBodyReady()) return; if(mPreviewMode == enable) diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 95b956907c..41caae4e52 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -141,7 +141,7 @@ namespace MWWorld /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. void equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor); - ///< \note \a iterator can be an end-iterator + ///< \warning \a iterator can not be an end()-iterator, use unequip function instead void setSelectedEnchantItem(const ContainerStoreIterator& iterator); ///< set the selected magic item (for using enchantments of type "Cast once" or "Cast when used")