|
|
@ -8,6 +8,7 @@
|
|
|
|
#include <components/misc/mathutil.hpp>
|
|
|
|
#include <components/misc/mathutil.hpp>
|
|
|
|
|
|
|
|
|
|
|
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
|
|
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
|
|
|
|
|
|
|
#include <components/detournavigator/navigator.hpp>
|
|
|
|
|
|
|
|
|
|
|
|
#include "../mwphysics/collisiontype.hpp"
|
|
|
|
#include "../mwphysics/collisiontype.hpp"
|
|
|
|
|
|
|
|
|
|
|
@ -127,10 +128,11 @@ namespace MWMechanics
|
|
|
|
{
|
|
|
|
{
|
|
|
|
//Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame.
|
|
|
|
//Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame.
|
|
|
|
updateLOS(actor, target, duration, storage);
|
|
|
|
updateLOS(actor, target, duration, storage);
|
|
|
|
float targetReachedTolerance = 0.0f;
|
|
|
|
const float targetReachedTolerance = storage.mLOS && !storage.mUseCustomDestination
|
|
|
|
if (storage.mLOS)
|
|
|
|
? storage.mAttackRange : 0.0f;
|
|
|
|
targetReachedTolerance = storage.mAttackRange;
|
|
|
|
const osg::Vec3f destination = storage.mUseCustomDestination
|
|
|
|
const bool is_target_reached = pathTo(actor, target.getRefData().getPosition().asVec3(), duration, targetReachedTolerance);
|
|
|
|
? storage.mCustomDestination : target.getRefData().getPosition().asVec3();
|
|
|
|
|
|
|
|
const bool is_target_reached = pathTo(actor, destination, duration, targetReachedTolerance);
|
|
|
|
if (is_target_reached) storage.mReadyToAttack = true;
|
|
|
|
if (is_target_reached) storage.mReadyToAttack = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -232,8 +234,8 @@ namespace MWMechanics
|
|
|
|
const ESM::Weapon* weapon = currentAction->getWeapon();
|
|
|
|
const ESM::Weapon* weapon = currentAction->getWeapon();
|
|
|
|
|
|
|
|
|
|
|
|
ESM::Position pos = actor.getRefData().getPosition();
|
|
|
|
ESM::Position pos = actor.getRefData().getPosition();
|
|
|
|
osg::Vec3f vActorPos(pos.asVec3());
|
|
|
|
const osg::Vec3f vActorPos(pos.asVec3());
|
|
|
|
osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3());
|
|
|
|
const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3());
|
|
|
|
|
|
|
|
|
|
|
|
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
|
|
|
|
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
|
|
|
|
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
|
|
|
|
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
|
|
|
@ -243,9 +245,7 @@ namespace MWMechanics
|
|
|
|
if (isRangedCombat)
|
|
|
|
if (isRangedCombat)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// rotate actor taking into account target movement direction and projectile speed
|
|
|
|
// rotate actor taking into account target movement direction and projectile speed
|
|
|
|
osg::Vec3f& lastTargetPos = storage.mLastTargetPos;
|
|
|
|
vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
|
|
|
|
vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
|
|
|
|
|
|
|
|
lastTargetPos = vTargetPos;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
|
|
|
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
|
|
|
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
|
|
|
|
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
|
|
|
@ -256,28 +256,66 @@ namespace MWMechanics
|
|
|
|
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
|
|
|
|
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
storage.mLastTargetPos = vTargetPos;
|
|
|
|
|
|
|
|
|
|
|
|
if (storage.mReadyToAttack)
|
|
|
|
if (storage.mReadyToAttack)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
|
|
|
|
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
|
|
|
|
// start new attack
|
|
|
|
// start new attack
|
|
|
|
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
|
|
|
|
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!isRangedCombat && !mPathFinder.isPathConstructed() && storage.mCurrentAction->isAttackingOrSpell())
|
|
|
|
|
|
|
|
|
|
|
|
// If actor uses custom destination it has to try to rebuild path because environment can change
|
|
|
|
|
|
|
|
// (door is opened between actor and target) or target position has changed and current custom destination
|
|
|
|
|
|
|
|
// is not good enough to attack target.
|
|
|
|
|
|
|
|
if (storage.mCurrentAction->isAttackingOrSpell()
|
|
|
|
|
|
|
|
&& ((!storage.mReadyToAttack && !mPathFinder.isPathConstructed())
|
|
|
|
|
|
|
|
|| (storage.mUseCustomDestination && (storage.mCustomDestination - vTargetPos).length() > rangeAttack)))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
const osg::Vec3f position = actor.getRefData().getPosition().asVec3();
|
|
|
|
// Try to build path to the target.
|
|
|
|
const osg::Vec3f destination = target.getRefData().getPosition().asVec3();
|
|
|
|
const auto halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor);
|
|
|
|
const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor);
|
|
|
|
const auto navigatorFlags = getNavigatorFlags(actor);
|
|
|
|
mPathFinder.buildPath(actor, position, destination, actor.getCell(), getPathGridGraph(actor.getCell()),
|
|
|
|
const auto areaCosts = getAreaCosts(actor);
|
|
|
|
halfExtents, getNavigatorFlags(actor), getAreaCosts(actor));
|
|
|
|
const auto pathGridGraph = getPathGridGraph(actor.getCell());
|
|
|
|
|
|
|
|
mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts);
|
|
|
|
|
|
|
|
|
|
|
|
if (!mPathFinder.isPathConstructed())
|
|
|
|
if (!mPathFinder.isPathConstructed())
|
|
|
|
{
|
|
|
|
{
|
|
|
|
storage.stopAttack();
|
|
|
|
// If there is no path, try to find a point on a line from the actor position to target projected
|
|
|
|
characterController.setAttackingOrSpell(false);
|
|
|
|
// on navmesh to attack the target from there.
|
|
|
|
currentAction.reset(new ActionFlee());
|
|
|
|
const MWBase::World* world = MWBase::Environment::get().getWorld();
|
|
|
|
actionCooldown = currentAction->getActionCooldown();
|
|
|
|
const auto halfExtents = world->getPathfindingHalfExtents(actor);
|
|
|
|
storage.startFleeing();
|
|
|
|
const auto navigator = world->getNavigator();
|
|
|
|
MWBase::Environment::get().getDialogueManager()->say(actor, "flee");
|
|
|
|
const auto navigatorFlags = getNavigatorFlags(actor);
|
|
|
|
|
|
|
|
const auto areaCosts = getAreaCosts(actor);
|
|
|
|
|
|
|
|
const auto hit = navigator->raycast(halfExtents, vActorPos, vTargetPos, navigatorFlags);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// If the point is close enough, try to find a path to that point.
|
|
|
|
|
|
|
|
mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts);
|
|
|
|
|
|
|
|
if (mPathFinder.isPathConstructed())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// If path to that point is found use it as custom destination.
|
|
|
|
|
|
|
|
storage.mCustomDestination = *hit;
|
|
|
|
|
|
|
|
storage.mUseCustomDestination = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!mPathFinder.isPathConstructed())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
storage.mUseCustomDestination = false;
|
|
|
|
|
|
|
|
storage.stopAttack();
|
|
|
|
|
|
|
|
characterController.setAttackingOrSpell(false);
|
|
|
|
|
|
|
|
currentAction.reset(new ActionFlee());
|
|
|
|
|
|
|
|
actionCooldown = currentAction->getActionCooldown();
|
|
|
|
|
|
|
|
storage.startFleeing();
|
|
|
|
|
|
|
|
MWBase::Environment::get().getDialogueManager()->say(actor, "flee");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
storage.mUseCustomDestination = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|