mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-01 03:15:34 +00:00
Implement fleeing AI (Closes #1118)
This commit is contained in:
parent
02b9e81f89
commit
5e46121046
12 changed files with 599 additions and 56 deletions
|
@ -560,6 +560,12 @@ namespace MWBase
|
|||
virtual void removeContainerScripts(const MWWorld::Ptr& reference) = 0;
|
||||
|
||||
virtual bool isPlayerInJail() const = 0;
|
||||
|
||||
/// Return terrain height at \a worldPos position.
|
||||
virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const = 0;
|
||||
|
||||
/// Return physical or rendering half extents of the given actor.
|
||||
virtual osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering=false) const = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "character.hpp"
|
||||
#include "aicombataction.hpp"
|
||||
#include "combat.hpp"
|
||||
#include "coordinateconverter.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -50,6 +51,19 @@ namespace MWMechanics
|
|||
bool mForceNoShortcut;
|
||||
ESM::Position mShortcutFailPos;
|
||||
MWMechanics::Movement mMovement;
|
||||
|
||||
enum FleeState
|
||||
{
|
||||
FleeState_None,
|
||||
FleeState_Idle,
|
||||
FleeState_RunBlindly,
|
||||
FleeState_RunToDestination
|
||||
};
|
||||
FleeState mFleeState;
|
||||
bool mFleeLOS;
|
||||
float mFleeUpdateLOSTimer;
|
||||
float mFleeBlindRunTimer;
|
||||
ESM::Pathgrid::Point mFleeDest;
|
||||
|
||||
AiCombatStorage():
|
||||
mAttackCooldown(0),
|
||||
|
@ -66,7 +80,11 @@ namespace MWMechanics
|
|||
mStrength(),
|
||||
mForceNoShortcut(false),
|
||||
mShortcutFailPos(),
|
||||
mMovement()
|
||||
mMovement(),
|
||||
mFleeState(FleeState_None),
|
||||
mFleeLOS(false),
|
||||
mFleeUpdateLOSTimer(0.0f),
|
||||
mFleeBlindRunTimer(0.0f)
|
||||
{}
|
||||
|
||||
void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
|
||||
|
@ -76,6 +94,10 @@ namespace MWMechanics
|
|||
const ESM::Weapon* weapon, bool distantCombat);
|
||||
void updateAttack(CharacterController& characterController);
|
||||
void stopAttack();
|
||||
|
||||
void startFleeing();
|
||||
void stopFleeing();
|
||||
bool isFleeing();
|
||||
};
|
||||
|
||||
AiCombat::AiCombat(const MWWorld::Ptr& actor) :
|
||||
|
@ -157,16 +179,23 @@ namespace MWMechanics
|
|||
|| target.getClass().getCreatureStats(target).isDead())
|
||||
return true;
|
||||
|
||||
if (storage.mCurrentAction.get()) // need to wait to init action with it's attack range
|
||||
if (!storage.isFleeing())
|
||||
{
|
||||
//Update every frame
|
||||
bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, storage.mAttackRange);
|
||||
if (is_target_reached) storage.mReadyToAttack = true;
|
||||
}
|
||||
if (storage.mCurrentAction.get()) // need to wait to init action with it's attack range
|
||||
{
|
||||
//Update every frame
|
||||
bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, storage.mAttackRange);
|
||||
if (is_target_reached) storage.mReadyToAttack = true;
|
||||
}
|
||||
|
||||
storage.updateCombatMove(duration);
|
||||
if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage);
|
||||
storage.updateAttack(characterController);
|
||||
storage.updateCombatMove(duration);
|
||||
if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage);
|
||||
storage.updateAttack(characterController);
|
||||
}
|
||||
else
|
||||
{
|
||||
updateFleeing(actor, target, duration, storage);
|
||||
}
|
||||
storage.mActionCooldown -= duration;
|
||||
|
||||
float& timerReact = storage.mTimerReact;
|
||||
|
@ -185,12 +214,6 @@ namespace MWMechanics
|
|||
|
||||
void AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController)
|
||||
{
|
||||
if (isTargetMagicallyHidden(target))
|
||||
{
|
||||
storage.stopAttack();
|
||||
return; // TODO: run away instead of doing nothing
|
||||
}
|
||||
|
||||
const MWWorld::CellStore*& currentCell = storage.mCell;
|
||||
bool cellChange = currentCell && (actor.getCell() != currentCell);
|
||||
if(!currentCell || cellChange)
|
||||
|
@ -198,30 +221,61 @@ namespace MWMechanics
|
|||
currentCell = actor.getCell();
|
||||
}
|
||||
|
||||
bool forceFlee = false;
|
||||
if (!canFight(actor, target))
|
||||
{
|
||||
storage.stopAttack();
|
||||
characterController.setAttackingOrSpell(false);
|
||||
storage.mActionCooldown = 0.f;
|
||||
forceFlee = true;
|
||||
}
|
||||
|
||||
const MWWorld::Class& actorClass = actor.getClass();
|
||||
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
||||
|
||||
float& actionCooldown = storage.mActionCooldown;
|
||||
if (actionCooldown > 0)
|
||||
return;
|
||||
|
||||
float &rangeAttack = storage.mAttackRange;
|
||||
boost::shared_ptr<Action>& currentAction = storage.mCurrentAction;
|
||||
if (characterController.readyToPrepareAttack())
|
||||
|
||||
if (!forceFlee)
|
||||
{
|
||||
currentAction = prepareNextAction(actor, target);
|
||||
if (actionCooldown > 0)
|
||||
return;
|
||||
|
||||
if (characterController.readyToPrepareAttack())
|
||||
{
|
||||
currentAction = prepareNextAction(actor, target);
|
||||
actionCooldown = currentAction->getActionCooldown();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currentAction.reset(new ActionFlee());
|
||||
actionCooldown = currentAction->getActionCooldown();
|
||||
}
|
||||
|
||||
const ESM::Weapon *weapon = NULL;
|
||||
bool isRangedCombat = false;
|
||||
if (currentAction.get())
|
||||
if (!currentAction)
|
||||
return;
|
||||
|
||||
if (storage.isFleeing() != currentAction->isFleeing())
|
||||
{
|
||||
rangeAttack = currentAction->getCombatRange(isRangedCombat);
|
||||
// Get weapon characteristics
|
||||
weapon = currentAction->getWeapon();
|
||||
if (currentAction->isFleeing())
|
||||
{
|
||||
storage.startFleeing();
|
||||
MWBase::Environment::get().getDialogueManager()->say(actor, "flee");
|
||||
return;
|
||||
}
|
||||
else
|
||||
storage.stopFleeing();
|
||||
}
|
||||
|
||||
bool isRangedCombat = false;
|
||||
float &rangeAttack = storage.mAttackRange;
|
||||
|
||||
rangeAttack = currentAction->getCombatRange(isRangedCombat);
|
||||
|
||||
// Get weapon characteristics
|
||||
const ESM::Weapon* weapon = currentAction->getWeapon();
|
||||
|
||||
ESM::Position pos = actor.getRefData().getPosition();
|
||||
osg::Vec3f vActorPos(pos.asVec3());
|
||||
osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3());
|
||||
|
@ -229,19 +283,7 @@ namespace MWMechanics
|
|||
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
|
||||
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
|
||||
|
||||
if (!currentAction)
|
||||
return;
|
||||
|
||||
storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack);
|
||||
|
||||
// can't fight if attacker can't go where target is. E.g. A fish can't attack person on land.
|
||||
if (distToTarget > rangeAttack
|
||||
&& !actorClass.isNpc() && !MWMechanics::isEnvironmentCompatible(actor, target))
|
||||
{
|
||||
// TODO: start fleeing?
|
||||
storage.stopAttack();
|
||||
return;
|
||||
}
|
||||
|
||||
if (storage.mReadyToAttack)
|
||||
{
|
||||
|
@ -267,6 +309,106 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
void MWMechanics::AiCombat::updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage)
|
||||
{
|
||||
static const float LOS_UPDATE_DURATION = 0.5f;
|
||||
static const float BLIND_RUN_DURATION = 1.0f;
|
||||
|
||||
if (storage.mFleeUpdateLOSTimer <= 0.f)
|
||||
{
|
||||
storage.mFleeLOS = MWBase::Environment::get().getWorld()->getLOS(actor, target);
|
||||
storage.mFleeUpdateLOSTimer = LOS_UPDATE_DURATION;
|
||||
}
|
||||
else
|
||||
storage.mFleeUpdateLOSTimer -= duration;
|
||||
|
||||
AiCombatStorage::FleeState& state = storage.mFleeState;
|
||||
switch (state)
|
||||
{
|
||||
case AiCombatStorage::FleeState_None:
|
||||
return;
|
||||
|
||||
case AiCombatStorage::FleeState_Idle:
|
||||
{
|
||||
float triggerDist = getMaxAttackDistance(target);
|
||||
|
||||
if (storage.mFleeLOS &&
|
||||
(triggerDist >= 1000 || getDistanceMinusHalfExtents(actor, target) <= triggerDist))
|
||||
{
|
||||
const ESM::Pathgrid* pathgrid =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*storage.mCell->getCell());
|
||||
|
||||
bool runFallback = true;
|
||||
|
||||
if (pathgrid && !actor.getClass().isPureWaterCreature(actor))
|
||||
{
|
||||
ESM::Pathgrid::PointList points;
|
||||
CoordinateConverter coords(storage.mCell->getCell());
|
||||
|
||||
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
|
||||
coords.toLocal(localPos);
|
||||
|
||||
int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, localPos);
|
||||
for (int i = 0; i < static_cast<int>(pathgrid->mPoints.size()); i++)
|
||||
{
|
||||
if (i != closestPointIndex && storage.mCell->isPointConnected(closestPointIndex, i))
|
||||
{
|
||||
points.push_back(pathgrid->mPoints[static_cast<size_t>(i)]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!points.empty())
|
||||
{
|
||||
ESM::Pathgrid::Point dest = points[Misc::Rng::rollDice(points.size())];
|
||||
coords.toWorld(dest);
|
||||
|
||||
state = AiCombatStorage::FleeState_RunToDestination;
|
||||
storage.mFleeDest = ESM::Pathgrid::Point(dest.mX, dest.mY, dest.mZ);
|
||||
|
||||
runFallback = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (runFallback)
|
||||
{
|
||||
state = AiCombatStorage::FleeState_RunBlindly;
|
||||
storage.mFleeBlindRunTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case AiCombatStorage::FleeState_RunBlindly:
|
||||
{
|
||||
// timer to prevent twitchy movement that can be observed in vanilla MW
|
||||
if (storage.mFleeBlindRunTimer < BLIND_RUN_DURATION)
|
||||
{
|
||||
storage.mFleeBlindRunTimer += duration;
|
||||
|
||||
storage.mMovement.mRotation[2] = osg::PI + getZAngleToDir(target.getRefData().getPosition().asVec3()-actor.getRefData().getPosition().asVec3());
|
||||
storage.mMovement.mPosition[1] = 1;
|
||||
updateActorsMovement(actor, duration, storage);
|
||||
}
|
||||
else
|
||||
state = AiCombatStorage::FleeState_Idle;
|
||||
}
|
||||
break;
|
||||
|
||||
case AiCombatStorage::FleeState_RunToDestination:
|
||||
{
|
||||
static const float fFleeDistance = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fFleeDistance")->getFloat();
|
||||
|
||||
float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
|
||||
if ((dist > fFleeDistance && !storage.mFleeLOS)
|
||||
|| pathTo(actor, storage.mFleeDest, duration))
|
||||
{
|
||||
state = AiCombatStorage::FleeState_Idle;
|
||||
}
|
||||
}
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage)
|
||||
{
|
||||
// apply combat movement
|
||||
|
@ -446,6 +588,29 @@ namespace MWMechanics
|
|||
mReadyToAttack = false;
|
||||
mAttack = false;
|
||||
}
|
||||
|
||||
void AiCombatStorage::startFleeing()
|
||||
{
|
||||
stopFleeing();
|
||||
mFleeState = FleeState_Idle;
|
||||
}
|
||||
|
||||
void AiCombatStorage::stopFleeing()
|
||||
{
|
||||
mMovement.mPosition[0] = 0;
|
||||
mMovement.mPosition[1] = 0;
|
||||
mMovement.mPosition[2] = 0;
|
||||
mFleeState = FleeState_None;
|
||||
mFleeDest = ESM::Pathgrid::Point(0, 0, 0);
|
||||
mFleeLOS = false;
|
||||
mFleeUpdateLOSTimer = 0.0f;
|
||||
mFleeUpdateLOSTimer = 0.0f;
|
||||
}
|
||||
|
||||
bool AiCombatStorage::isFleeing()
|
||||
{
|
||||
return mFleeState != FleeState_None;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -61,6 +61,8 @@ namespace MWMechanics
|
|||
|
||||
void attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController);
|
||||
|
||||
void updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage);
|
||||
|
||||
/// Transfer desired movement (from AiCombatStorage) to Actor
|
||||
void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage);
|
||||
void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,18 @@ namespace MWMechanics
|
|||
virtual float getActionCooldown() { return 0.f; }
|
||||
virtual const ESM::Weapon* getWeapon() const { return NULL; };
|
||||
virtual bool isAttackingOrSpell() const { return true; }
|
||||
virtual bool isFleeing() const { return false; }
|
||||
};
|
||||
|
||||
class ActionFlee : public Action
|
||||
{
|
||||
public:
|
||||
ActionFlee() {}
|
||||
virtual void prepare(const MWWorld::Ptr& actor) {}
|
||||
virtual float getCombatRange (bool& isRanged) const { return 0.0f; }
|
||||
virtual float getActionCooldown() { return 3.0f; }
|
||||
virtual bool isAttackingOrSpell() const { return false; }
|
||||
virtual bool isFleeing() const { return true; }
|
||||
};
|
||||
|
||||
class ActionSpell : public Action
|
||||
|
@ -89,6 +101,15 @@ namespace MWMechanics
|
|||
float rateEffects (const ESM::EffectList& list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy);
|
||||
|
||||
boost::shared_ptr<Action> prepareNextAction (const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy);
|
||||
|
||||
float getDistanceMinusHalfExtents(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, bool minusZDist=false);
|
||||
float getMaxAttackDistance(const MWWorld::Ptr& actor);
|
||||
bool canFight(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy);
|
||||
|
||||
float vanillaRateSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy);
|
||||
float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy);
|
||||
float vanillaRateFlee(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy);
|
||||
bool makeFleeDecision(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, float antiFleeRating);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -431,4 +431,19 @@ namespace MWMechanics
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2)
|
||||
{
|
||||
osg::Vec3f pos1 (actor1.getRefData().getPosition().asVec3());
|
||||
osg::Vec3f pos2 (actor2.getRefData().getPosition().asVec3());
|
||||
|
||||
float d = (pos1 - pos2).length();
|
||||
|
||||
static const int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"iFightDistanceBase")->getInt();
|
||||
static const float fFightDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"fFightDistanceMultiplier")->getFloat();
|
||||
|
||||
return (iFightDistanceBase - fFightDistanceMultiplier * d);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ void applyFatigueLoss(const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon,
|
|||
/// e.g. If attacker is a fish, is victim in water? Or, if attacker can't swim, is victim on land?
|
||||
bool isEnvironmentCompatible(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim);
|
||||
|
||||
float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -25,25 +25,11 @@
|
|||
#include "autocalcspell.hpp"
|
||||
#include "npcstats.hpp"
|
||||
#include "actorutil.hpp"
|
||||
#include "combat.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2)
|
||||
{
|
||||
osg::Vec3f pos1 (actor1.getRefData().getPosition().asVec3());
|
||||
osg::Vec3f pos2 (actor2.getRefData().getPosition().asVec3());
|
||||
|
||||
float d = (pos1 - pos2).length();
|
||||
|
||||
static const int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"iFightDistanceBase")->getInt();
|
||||
static const float fFightDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"fFightDistanceMultiplier")->getFloat();
|
||||
|
||||
return (iFightDistanceBase - fFightDistanceMultiplier * d);
|
||||
}
|
||||
|
||||
float getFightDispositionBias(float disposition)
|
||||
{
|
||||
static const float fFightDispMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
|
|
|
@ -393,7 +393,26 @@ namespace MWWorld
|
|||
|
||||
bool Class::isPureWaterCreature(const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
return canSwim(ptr) && !canWalk(ptr);
|
||||
return canSwim(ptr)
|
||||
&& !isBipedal(ptr)
|
||||
&& !canFly(ptr)
|
||||
&& !canWalk(ptr);
|
||||
}
|
||||
|
||||
bool Class::isPureFlyingCreature(const Ptr& ptr) const
|
||||
{
|
||||
return canFly(ptr)
|
||||
&& !isBipedal(ptr)
|
||||
&& !canSwim(ptr)
|
||||
&& !canWalk(ptr);
|
||||
}
|
||||
|
||||
bool Class::isPureLandCreature(const Ptr& ptr) const
|
||||
{
|
||||
return canWalk(ptr)
|
||||
&& !isBipedal(ptr)
|
||||
&& !canFly(ptr)
|
||||
&& !canSwim(ptr);
|
||||
}
|
||||
|
||||
bool Class::isMobile(const MWWorld::Ptr& ptr) const
|
||||
|
|
|
@ -312,6 +312,8 @@ namespace MWWorld
|
|||
virtual bool canSwim(const MWWorld::ConstPtr& ptr) const;
|
||||
virtual bool canWalk(const MWWorld::ConstPtr& ptr) const;
|
||||
bool isPureWaterCreature(const MWWorld::Ptr& ptr) const;
|
||||
bool isPureFlyingCreature(const MWWorld::Ptr& ptr) const;
|
||||
bool isPureLandCreature(const MWWorld::Ptr& ptr) const;
|
||||
bool isMobile(const MWWorld::Ptr& ptr) const;
|
||||
|
||||
virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const;
|
||||
|
|
|
@ -421,11 +421,16 @@ namespace MWWorld
|
|||
gmst["sBribeFail"] = ESM::Variant("Bribe Fail");
|
||||
gmst["fNPCHealthBarTime"] = ESM::Variant(5.f);
|
||||
gmst["fNPCHealthBarFade"] = ESM::Variant(1.f);
|
||||
gmst["fFleeDistance"] = ESM::Variant(3000.f);
|
||||
|
||||
// Werewolf (BM)
|
||||
gmst["fWereWolfRunMult"] = ESM::Variant(1.f);
|
||||
gmst["fWereWolfSilverWeaponDamageMult"] = ESM::Variant(1.f);
|
||||
gmst["iWerewolfFightMod"] = ESM::Variant(1);
|
||||
gmst["iWereWolfFleeMod"] = ESM::Variant(100);
|
||||
gmst["iWereWolfLevelToAttack"] = ESM::Variant(20);
|
||||
gmst["iWereWolfBounty"] = ESM::Variant(10000);
|
||||
gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f);
|
||||
|
||||
std::map<std::string, ESM::Variant> globals;
|
||||
// vanilla Morrowind does not define dayspassed.
|
||||
|
@ -1295,7 +1300,7 @@ namespace MWWorld
|
|||
|
||||
float terrainHeight = -std::numeric_limits<float>::max();
|
||||
if (ptr.getCell()->isExterior())
|
||||
terrainHeight = mRendering->getTerrainHeightAt(pos.asVec3());
|
||||
terrainHeight = getTerrainHeightAt(pos.asVec3());
|
||||
|
||||
if (pos.pos[2] < terrainHeight)
|
||||
pos.pos[2] = terrainHeight;
|
||||
|
@ -3121,6 +3126,19 @@ namespace MWWorld
|
|||
return MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail);
|
||||
}
|
||||
|
||||
float World::getTerrainHeightAt(const osg::Vec3f& worldPos) const
|
||||
{
|
||||
return mRendering->getTerrainHeightAt(worldPos);
|
||||
}
|
||||
|
||||
osg::Vec3f World::getHalfExtents(const ConstPtr& actor, bool rendering) const
|
||||
{
|
||||
if (rendering)
|
||||
return mPhysics->getRenderingHalfExtents(actor);
|
||||
else
|
||||
return mPhysics->getHalfExtents(actor);
|
||||
}
|
||||
|
||||
void World::spawnRandomCreature(const std::string &creatureList)
|
||||
{
|
||||
const ESM::CreatureLevList* list = getStore().get<ESM::CreatureLevList>().find(creatureList);
|
||||
|
|
|
@ -661,6 +661,12 @@ namespace MWWorld
|
|||
virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target);
|
||||
|
||||
virtual bool isPlayerInJail() const;
|
||||
|
||||
/// Return terrain height at \a worldPos position.
|
||||
virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const;
|
||||
|
||||
/// Return physical or rendering half extents of the given actor.
|
||||
virtual osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering=false) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue