|
|
|
@ -5,14 +5,17 @@
|
|
|
|
|
|
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
|
|
|
#include "../mwbase/world.hpp"
|
|
|
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
|
|
|
|
|
|
|
|
|
#include "../mwworld/class.hpp"
|
|
|
|
|
#include "../mwworld/esmstore.hpp"
|
|
|
|
|
#include "../mwworld/inventorystore.hpp"
|
|
|
|
|
#include "../mwworld/actionequip.hpp"
|
|
|
|
|
#include "../mwworld/cellstore.hpp"
|
|
|
|
|
|
|
|
|
|
#include "npcstats.hpp"
|
|
|
|
|
#include "spellcasting.hpp"
|
|
|
|
|
#include "combat.hpp"
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
@ -517,6 +520,7 @@ namespace MWMechanics
|
|
|
|
|
Spells& spells = actor.getClass().getCreatureStats(actor).getSpells();
|
|
|
|
|
|
|
|
|
|
float bestActionRating = 0.f;
|
|
|
|
|
float antiFleeRating = 0.f;
|
|
|
|
|
// Default to hand-to-hand combat
|
|
|
|
|
boost::shared_ptr<Action> bestAction (new ActionWeapon(MWWorld::Ptr()));
|
|
|
|
|
if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
|
|
|
|
@ -536,6 +540,7 @@ namespace MWMechanics
|
|
|
|
|
{
|
|
|
|
|
bestActionRating = rating;
|
|
|
|
|
bestAction.reset(new ActionPotion(*it));
|
|
|
|
|
antiFleeRating = std::numeric_limits<float>::max();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -546,6 +551,7 @@ namespace MWMechanics
|
|
|
|
|
{
|
|
|
|
|
bestActionRating = rating;
|
|
|
|
|
bestAction.reset(new ActionEnchantedItem(it));
|
|
|
|
|
antiFleeRating = std::numeric_limits<float>::max();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -593,6 +599,7 @@ namespace MWMechanics
|
|
|
|
|
|
|
|
|
|
bestActionRating = rating;
|
|
|
|
|
bestAction.reset(new ActionWeapon(*it, ammo));
|
|
|
|
|
antiFleeRating = vanillaRateWeaponAndAmmo(*it, ammo, actor, enemy);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -606,13 +613,308 @@ namespace MWMechanics
|
|
|
|
|
{
|
|
|
|
|
bestActionRating = rating;
|
|
|
|
|
bestAction.reset(new ActionSpell(spell->mId));
|
|
|
|
|
antiFleeRating = vanillaRateSpell(spell, actor, enemy);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (makeFleeDecision(actor, enemy, antiFleeRating))
|
|
|
|
|
bestAction.reset(new ActionFlee());
|
|
|
|
|
|
|
|
|
|
if (bestAction.get())
|
|
|
|
|
bestAction->prepare(actor);
|
|
|
|
|
|
|
|
|
|
return bestAction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float getDistanceMinusHalfExtents(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool minusZDist)
|
|
|
|
|
{
|
|
|
|
|
osg::Vec3f actor1Pos = actor1.getRefData().getPosition().asVec3();
|
|
|
|
|
osg::Vec3f actor2Pos = actor2.getRefData().getPosition().asVec3();
|
|
|
|
|
|
|
|
|
|
float dist = (actor1Pos - actor2Pos).length();
|
|
|
|
|
|
|
|
|
|
if (minusZDist)
|
|
|
|
|
dist -= std::abs(actor1Pos.z() - actor2Pos.z());
|
|
|
|
|
|
|
|
|
|
return (dist
|
|
|
|
|
- MWBase::Environment::get().getWorld()->getHalfExtents(actor1).y()
|
|
|
|
|
- MWBase::Environment::get().getWorld()->getHalfExtents(actor2).y());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float getMaxAttackDistance(const MWWorld::Ptr& actor)
|
|
|
|
|
{
|
|
|
|
|
const CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
|
|
|
|
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
|
|
|
|
|
|
|
|
|
std::string selectedSpellId = stats.getSpells().getSelectedSpell();
|
|
|
|
|
MWWorld::Ptr selectedEnchItem;
|
|
|
|
|
|
|
|
|
|
MWWorld::Ptr activeWeapon, activeAmmo;
|
|
|
|
|
if (actor.getClass().hasInventoryStore(actor))
|
|
|
|
|
{
|
|
|
|
|
MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor);
|
|
|
|
|
|
|
|
|
|
MWWorld::ContainerStoreIterator item = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
|
|
|
|
if (item != invStore.end() && item.getType() == MWWorld::ContainerStore::Type_Weapon)
|
|
|
|
|
activeWeapon = *item;
|
|
|
|
|
|
|
|
|
|
item = invStore.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
|
|
|
|
if (item != invStore.end() && item.getType() == MWWorld::ContainerStore::Type_Weapon)
|
|
|
|
|
activeAmmo = *item;
|
|
|
|
|
|
|
|
|
|
if (invStore.getSelectedEnchantItem() != invStore.end())
|
|
|
|
|
selectedEnchItem = *invStore.getSelectedEnchantItem();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float dist = 1.0f;
|
|
|
|
|
if (activeWeapon.isEmpty() && !selectedSpellId.empty() && !selectedEnchItem.isEmpty())
|
|
|
|
|
{
|
|
|
|
|
static const float fHandToHandReach = gmst.find("fHandToHandReach")->getFloat();
|
|
|
|
|
dist = fHandToHandReach;
|
|
|
|
|
}
|
|
|
|
|
else if (stats.getDrawState() == MWMechanics::DrawState_Spell)
|
|
|
|
|
{
|
|
|
|
|
dist = 1.0f;
|
|
|
|
|
if (!selectedSpellId.empty())
|
|
|
|
|
{
|
|
|
|
|
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(selectedSpellId);
|
|
|
|
|
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt =
|
|
|
|
|
spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt)
|
|
|
|
|
{
|
|
|
|
|
if (effectIt->mArea == ESM::RT_Target)
|
|
|
|
|
{
|
|
|
|
|
const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectIt->mEffectID);
|
|
|
|
|
dist = effect->mData.mSpeed;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (!selectedEnchItem.isEmpty())
|
|
|
|
|
{
|
|
|
|
|
std::string enchId = selectedEnchItem.getClass().getEnchantment(selectedEnchItem);
|
|
|
|
|
if (!enchId.empty())
|
|
|
|
|
{
|
|
|
|
|
const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchId);
|
|
|
|
|
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt =
|
|
|
|
|
ench->mEffects.mList.begin(); effectIt != ench->mEffects.mList.end(); ++effectIt)
|
|
|
|
|
{
|
|
|
|
|
if (effectIt->mArea == ESM::RT_Target)
|
|
|
|
|
{
|
|
|
|
|
const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectIt->mEffectID);
|
|
|
|
|
dist = effect->mData.mSpeed;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const float fTargetSpellMaxSpeed = gmst.find("fTargetSpellMaxSpeed")->getFloat();
|
|
|
|
|
dist *= std::max(1000.0f, fTargetSpellMaxSpeed);
|
|
|
|
|
}
|
|
|
|
|
else if (!activeWeapon.isEmpty())
|
|
|
|
|
{
|
|
|
|
|
const ESM::Weapon* esmWeap = activeWeapon.get<ESM::Weapon>()->mBase;
|
|
|
|
|
if (esmWeap->mData.mType >= ESM::Weapon::MarksmanBow)
|
|
|
|
|
{
|
|
|
|
|
static const float fTargetSpellMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat();
|
|
|
|
|
dist = fTargetSpellMaxSpeed;
|
|
|
|
|
if (!activeAmmo.isEmpty())
|
|
|
|
|
{
|
|
|
|
|
const ESM::Weapon* esmAmmo = activeAmmo.get<ESM::Weapon>()->mBase;
|
|
|
|
|
dist *= esmAmmo->mData.mSpeed;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (esmWeap->mData.mReach > 1)
|
|
|
|
|
{
|
|
|
|
|
dist = esmWeap->mData.mReach;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dist = (dist > 0.f) ? dist : 1.0f;
|
|
|
|
|
|
|
|
|
|
static const float fCombatDistance = gmst.find("fCombatDistance")->getFloat();
|
|
|
|
|
static const float fCombatDistanceWerewolfMod = gmst.find("fCombatDistanceWerewolfMod")->getFloat();
|
|
|
|
|
|
|
|
|
|
float combatDistance = fCombatDistance;
|
|
|
|
|
if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
|
|
|
|
|
combatDistance *= (fCombatDistanceWerewolfMod + 1.0f);
|
|
|
|
|
|
|
|
|
|
if (dist < combatDistance)
|
|
|
|
|
dist *= combatDistance;
|
|
|
|
|
|
|
|
|
|
return dist;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool canFight(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy)
|
|
|
|
|
{
|
|
|
|
|
ESM::Position actorPos = actor.getRefData().getPosition();
|
|
|
|
|
ESM::Position enemyPos = enemy.getRefData().getPosition();
|
|
|
|
|
|
|
|
|
|
const CreatureStats& enemyStats = enemy.getClass().getCreatureStats(enemy);
|
|
|
|
|
if (enemyStats.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude() > 0
|
|
|
|
|
|| enemyStats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude() > 0)
|
|
|
|
|
{
|
|
|
|
|
if (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(enemy, actor))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (actor.getClass().isPureWaterCreature(actor))
|
|
|
|
|
{
|
|
|
|
|
if (!MWBase::Environment::get().getWorld()->isWading(enemy))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float atDist = getMaxAttackDistance(actor);
|
|
|
|
|
if (atDist > getDistanceMinusHalfExtents(actor, enemy)
|
|
|
|
|
&& atDist > std::abs(actorPos.pos[2] - enemyPos.pos[2]))
|
|
|
|
|
{
|
|
|
|
|
if (MWBase::Environment::get().getWorld()->getLOS(actor, enemy))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (actor.getClass().isPureFlyingCreature(actor) || actor.getClass().isPureLandCreature(actor))
|
|
|
|
|
{
|
|
|
|
|
if (MWBase::Environment::get().getWorld()->isSwimming(enemy))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (actor.getClass().isBipedal(actor) || !actor.getClass().canFly(actor))
|
|
|
|
|
{
|
|
|
|
|
if (enemy.getClass().getCreatureStats(enemy).getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0)
|
|
|
|
|
{
|
|
|
|
|
float attackDistance = getMaxAttackDistance(actor);
|
|
|
|
|
if ((attackDistance + actorPos.pos[2]) < enemyPos.pos[2])
|
|
|
|
|
{
|
|
|
|
|
if (enemy.getCell()->isExterior())
|
|
|
|
|
{
|
|
|
|
|
if (attackDistance < (enemyPos.pos[2] - MWBase::Environment::get().getWorld()->getTerrainHeightAt(enemyPos.asVec3())))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!actor.getClass().canWalk(actor) && !actor.getClass().isBipedal(actor))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
if (actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
if (MWBase::Environment::get().getWorld()->isSwimming(actor))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
if (getDistanceMinusHalfExtents(actor, enemy, true) <= 0.0f)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float vanillaRateSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy)
|
|
|
|
|
{
|
|
|
|
|
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
|
|
|
|
|
|
|
|
|
static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->getFloat();
|
|
|
|
|
static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->getFloat();
|
|
|
|
|
|
|
|
|
|
float mult = fAIMagicSpellMult;
|
|
|
|
|
|
|
|
|
|
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt =
|
|
|
|
|
spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt)
|
|
|
|
|
{
|
|
|
|
|
if (effectIt->mArea == ESM::RT_Target)
|
|
|
|
|
{
|
|
|
|
|
if (!MWBase::Environment::get().getWorld()->isSwimming(enemy))
|
|
|
|
|
mult = fAIRangeMagicSpellMult;
|
|
|
|
|
else
|
|
|
|
|
mult = 0.0f;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return MWMechanics::getSpellSuccessChance(spell, actor) * mult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy)
|
|
|
|
|
{
|
|
|
|
|
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
|
|
|
|
|
|
|
|
|
static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->getFloat();
|
|
|
|
|
static const float fAIMeleeArmorMult = gmst.find("fAIMeleeArmorMult")->getFloat();
|
|
|
|
|
static const float fAIRangeMeleeWeaponMult = gmst.find("fAIRangeMeleeWeaponMult")->getFloat();
|
|
|
|
|
|
|
|
|
|
if (weapon.isEmpty())
|
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
|
|
float skillMult = actor.getClass().getSkill(actor, weapon.getClass().getEquipmentSkill(weapon)) * 0.01f;
|
|
|
|
|
float chopMult = fAIMeleeWeaponMult;
|
|
|
|
|
float bonusDamage = 0.f;
|
|
|
|
|
|
|
|
|
|
const ESM::Weapon* esmWeap = weapon.get<ESM::Weapon>()->mBase;
|
|
|
|
|
|
|
|
|
|
if (esmWeap->mData.mType >= ESM::Weapon::MarksmanBow)
|
|
|
|
|
{
|
|
|
|
|
if (!ammo.isEmpty() && !MWBase::Environment::get().getWorld()->isSwimming(enemy))
|
|
|
|
|
{
|
|
|
|
|
bonusDamage = ammo.get<ESM::Weapon>()->mBase->mData.mChop[1];
|
|
|
|
|
chopMult = fAIRangeMeleeWeaponMult;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
chopMult = 0.f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float chopRating = (esmWeap->mData.mChop[1] + bonusDamage) * skillMult * chopMult;
|
|
|
|
|
float slashRating = esmWeap->mData.mSlash[1] * skillMult * fAIMeleeWeaponMult;
|
|
|
|
|
float thrustRating = esmWeap->mData.mThrust[1] * skillMult * fAIMeleeWeaponMult;
|
|
|
|
|
|
|
|
|
|
return actor.getClass().getArmorRating(actor) * fAIMeleeArmorMult
|
|
|
|
|
+ std::max(std::max(chopRating, slashRating), thrustRating);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float vanillaRateFlee(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy)
|
|
|
|
|
{
|
|
|
|
|
const CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
|
|
|
|
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
|
|
|
|
|
|
|
|
|
int flee = stats.getAiSetting(CreatureStats::AI_Flee).getModified();
|
|
|
|
|
if (flee >= 100)
|
|
|
|
|
return flee;
|
|
|
|
|
|
|
|
|
|
static const float fAIFleeHealthMult = gmst.find("fAIFleeHealthMult")->getFloat();
|
|
|
|
|
static const float fAIFleeFleeMult = gmst.find("fAIFleeFleeMult")->getFloat();
|
|
|
|
|
|
|
|
|
|
float healthPercentage = (stats.getHealth().getModified() == 0.0f)
|
|
|
|
|
? 1.0f : stats.getHealth().getCurrent() / stats.getHealth().getModified();
|
|
|
|
|
float rating = (1.0f - healthPercentage) * fAIFleeHealthMult + flee * fAIFleeFleeMult;
|
|
|
|
|
|
|
|
|
|
static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->getInt();
|
|
|
|
|
|
|
|
|
|
if (enemy.getClass().isNpc() && enemy.getClass().getNpcStats(enemy).isWerewolf() && stats.getLevel() < iWereWolfLevelToAttack)
|
|
|
|
|
{
|
|
|
|
|
static const int iWereWolfFleeMod = gmst.find("iWereWolfFleeMod")->getInt();
|
|
|
|
|
rating = iWereWolfFleeMod;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rating != 0.0f)
|
|
|
|
|
rating += getFightDistanceBias(actor, enemy);
|
|
|
|
|
|
|
|
|
|
return rating;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool makeFleeDecision(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, float antiFleeRating)
|
|
|
|
|
{
|
|
|
|
|
float fleeRating = vanillaRateFlee(actor, enemy);
|
|
|
|
|
if (fleeRating < 100.0f)
|
|
|
|
|
fleeRating = 0.0f;
|
|
|
|
|
|
|
|
|
|
if (fleeRating > antiFleeRating)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
// Run away after summoning a creature if we have nothing to use but fists.
|
|
|
|
|
if (antiFleeRating == 0.0f && !actor.getClass().getCreatureStats(actor).getSummonedCreatureMap().empty())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|