Merge remote-tracking branch 'scrawl/ai'

deque
Marc Zinnschlag 10 years ago
commit 9197d4b653

@ -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

@ -248,6 +248,12 @@ namespace MWMechanics
if (caster.getRefData().getHandle() == "player")
MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}");
const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
.search("VFX_Soul_Trap");
if (fx)
MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel,
"", Ogre::Vector3(mCreature.getRefData().getPosition().pos));
}
};

@ -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();
}
const ESM::Weapon *weapon = NULL;
MWMechanics::WeaponType weaptype;
float weapRange = 1.0f;
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 = WeapType_None;
float weapRange = 1.0f;
// 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,11 +304,11 @@ namespace MWMechanics
if (weaptype == WeapType_HandToHand)
{
static float fHandToHandReach =
static float fHandToHandReach =
world->getStore().get<ESM::GameSetting>().find("fHandToHandReach")->getFloat();
weapRange = fHandToHandReach;
}
else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell)
else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell && weaptype != WeapType_None)
{
// All other WeapTypes are actually weapons, so get<ESM::Weapon> is safe.
weapon = weaponSlot->get<ESM::Weapon>()->mBase;
@ -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,9 @@ 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
&& weaptype != MWMechanics::WeapType_None)
{
weapon = weaponSlot->get<ESM::Weapon>()->mBase;
weapSpeed = weapon->mData.mSpeed;

@ -14,6 +14,8 @@
#include "../mwbase/world.hpp"
#include <boost/shared_ptr.hpp>
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<Action> mCurrentAction;
float mActionCooldown;
void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
};
}

@ -0,0 +1,463 @@
#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 <components/esm/loadench.hpp>
#include <components/esm/loadmgef.hpp>
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<ESM::ENAMstruct>::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<ESM::Potion>()->mBase;
return rateEffects(potion->mEffects, actor, MWWorld::Ptr());
}
float rateWeapon (const MWWorld::Ptr &item, const MWWorld::Ptr& actor, const MWWorld::Ptr& target, int type,
float arrowRating, float boltRating)
{
if (item.getTypeName() != typeid(ESM::Weapon).name())
return 0.f;
const ESM::Weapon* weapon = item.get<ESM::Weapon>()->mBase;
if (type != -1 && weapon->mData.mType != type)
return 0.f;
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))
{
if (item.getClass().getItemHealth(item) == 0)
return 0.f;
rating *= item.getClass().getItemHealth(item) / float(item.getClass().getItemMaxHealth(item));
}
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)
{
if (boltRating <= 0.f)
rating = 0.f;
else
rating += boltRating;
}
if (!weapon->mEnchant.empty())
{
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().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);
}
int skill = item.getClass().getEquipmentSkill(item);
if (skill != -1)
rating *= actor.getClass().getSkill(actor, skill) / 100.f;
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<ESM::Enchantment>().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<float>& 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<ESM::MagicEffect>().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<ESM::ENAMstruct>::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<ESM::Spell>().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<ESM::Enchantment>().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);
}
if (!mAmmunition.isEmpty())
{
MWWorld::ActionEquip equip(mAmmunition);
equip.execute(actor);
}
}
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Weapon);
}
void ActionWeapon::getCombatRange(float& rangeAttack, float& rangeFollow)
{
// Already done in AiCombat itself
}
boost::shared_ptr<Action> 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<Action> 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));
}
}
float bestArrowRating = 0;
MWWorld::Ptr bestArrow;
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{
float rating = rateWeapon(*it, actor, target, ESM::Weapon::Arrow);
if (rating > bestArrowRating)
{
bestArrowRating = rating;
bestArrow = *it;
}
}
float bestBoltRating = 0;
MWWorld::Ptr bestBolt;
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{
float rating = rateWeapon(*it, actor, target, ESM::Weapon::Bolt);
if (rating > bestBoltRating)
{
bestBoltRating = rating;
bestBolt = *it;
}
}
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{
std::vector<int> equipmentSlots = it->getClass().getEquipmentSlots(*it).first;
if (std::find(equipmentSlots.begin(), equipmentSlots.end(), (int)MWWorld::InventoryStore::Slot_CarriedRight)
== equipmentSlots.end())
continue;
float rating = rateWeapon(*it, actor, target, -1, bestArrowRating, bestBoltRating);
if (rating > bestActionRating)
{
const ESM::Weapon* weapon = it->get<ESM::Weapon>()->mBase;
MWWorld::Ptr ammo;
if (weapon->mData.mType == ESM::Weapon::MarksmanBow)
ammo = bestArrow;
else if (weapon->mData.mType == ESM::Weapon::MarksmanCrossbow)
ammo = bestBolt;
bestActionRating = rating;
bestAction.reset(new ActionWeapon(*it, ammo));
}
}
}
for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().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;
}
}

@ -0,0 +1,89 @@
#ifndef OPENMW_AICOMBAT_ACTION_H
#define OPENMW_AICOMBAT_ACTION_H
#include <boost/shared_ptr.hpp>
#include "../mwworld/ptr.hpp"
#include "../mwworld/containerstore.hpp"
#include <components/esm/loadspel.hpp>
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
{
private:
MWWorld::Ptr mAmmunition;
MWWorld::Ptr mWeapon;
public:
/// \a weapon may be empty for hand-to-hand combat
ActionWeapon(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo = MWWorld::Ptr())
: mWeapon(weapon), mAmmunition(ammo) {}
/// 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);
/// @param type Skip all weapons that are not of this type (i.e. return rating 0)
float rateWeapon (const MWWorld::Ptr& item, const MWWorld::Ptr& actor, const MWWorld::Ptr& target,
int type=-1, float arrowRating=0.f, float boltRating=0.f);
/// @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<Action> prepareNextAction (const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
}
#endif

@ -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)
{

@ -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.

@ -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)

@ -198,11 +198,6 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
continue;
}
// Don't auto-equip probes or lockpicks. NPCs can't use them (yet). And AiCombat would attempt to "attack" with them.
// NOTE: In the future AiCombat should handle equipping appropriate weapons
if (test.getTypeName() == typeid(ESM::Lockpick).name() || test.getTypeName() == typeid(ESM::Probe).name())
continue;
// Only autoEquip if we are the original owner of the item.
// This stops merchants from auto equipping anything you sell to them.
// ...unless this is a companion, he should always equip items given to him.
@ -219,6 +214,10 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
for (std::vector<int>::const_iterator iter2 (itemsSlots.first.begin());
iter2!=itemsSlots.first.end(); ++iter2)
{
if (*iter2 == Slot_CarriedRight) // Items in right hand are situational use, so don't equip them.
// Equipping weapons is handled by AiCombat. Anything else (lockpicks, probes) can't be used by NPCs anyway (yet)
continue;
bool use = false;
if (slots_.at (*iter2)==end())

@ -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")

Loading…
Cancel
Save