1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-06-27 21:11:34 +00:00

refactor: move combat move and attack functions to AiCombatStorage

This commit is contained in:
mrcheko 2015-05-31 19:19:14 +03:00
parent 7b207a7954
commit 1d4be08f6e

View file

@ -27,14 +27,15 @@
#include "aicombataction.hpp"
#include "combat.hpp"
// forward declarations
namespace
{
//chooses an attack depending on probability to avoid uniformity
/// Choose an attack depending on probability to avoid uniformity
ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement);
/// Get an attack min/max attack duration for current actor's weapon
void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2]);
/// Calculate direction to aim at the moving target
Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos,
float duration, int weapType, float strength);
@ -49,34 +50,15 @@ namespace
return -Ogre::Math::ASin(dir.z / len).valueDegrees();
}
const float PATHFIND_Z_REACH = 50.0f;
// distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid
const float PATHFIND_CAUTION_DIST = 500.0f;
// distance after which actor (failed previously to shortcut) will try again
const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f;
// cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target;
// magnitude of pits/obstacles is defined by PATHFIND_Z_REACH
bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY)
{
if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z - to.z) <= PATHFIND_Z_REACH)
{
Ogre::Vector3 dir = to - from;
dir.z = 0;
dir.normalise();
float verticalOffset = 200; // instead of '200' here we want the height of the actor
Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset;
// cast up-down ray and find height in world space of hit
float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1);
if(std::abs(from.z - h) <= PATHFIND_Z_REACH)
return true;
}
return false;
}
/// Cast up-down ray to fo pits/obstacles on the way
/// \note magnitude of pits/obstacles is defined by PATHFIND_Z_REACH
bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY);
}
namespace MWMechanics
@ -108,24 +90,36 @@ namespace MWMechanics
MWMechanics::Movement mMovement;
AiCombatStorage():
mTimerAttack(0),
mTimerReact(0),
mTimerCombatMove(0),
mAttack(false),
mFollowTarget(false),
mCombatMove(false),
mReadyToAttack(false),
mForceNoShortcut(false),
mCell(NULL),
mCurrentAction(),
mActionCooldown(0),
mStrength(),
mMinMaxAttackDurationInitialised(false),
mLastTargetPos(0,0,0),
mLastActorPos(0,0,0),
mMovement(){}
mTimerAttack(0),
mTimerReact(0),
mTimerCombatMove(0),
mAttack(false),
mFollowTarget(false),
mCombatMove(false),
mReadyToAttack(false),
mForceNoShortcut(false),
mCell(NULL),
mCurrentAction(),
mActionCooldown(0),
mStrength(),
mMinMaxAttackDurationInitialised(false),
mLastTargetPos(0,0,0),
mLastActorPos(0,0,0),
mMovement()
{}
void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack);
void updateCombatMove(float duration);
void stopCombatMove();
void updateWeaponAttackDurations(const MWWorld::Ptr& actor);
void updateAttack(float duration, float attacksPeriod);
bool startAttack(bool isDistantCombat, const ESM::Weapon* weapon, float attacksPeriod);
/// Start special run attack taking into account actor-target speeds and weapon characteristics
void startRunAttack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float attacksPeriod, float distToTarget, float weapRange);
};
AiCombat::AiCombat(const MWWorld::Ptr& actor) :
mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId())
{}
@ -136,9 +130,7 @@ namespace MWMechanics
}
void AiCombat::init()
{
}
{}
/*
* Current AiCombat movement states (as of 0.29.0), ignoring the details of the
@ -191,8 +183,6 @@ namespace MWMechanics
// get or create temporary storage
AiCombatStorage& storage = state.get<AiCombatStorage>();
//General description
if(actor.getClass().getCreatureStats(actor).isDead())
return true;
@ -209,21 +199,10 @@ namespace MWMechanics
const MWWorld::Class& actorClass = actor.getClass();
MWBase::World* world = MWBase::Environment::get().getWorld();
//Update every frame
bool& combatMove = storage.mCombatMove;
float& timerCombatMove = storage.mTimerCombatMove;
storage.updateCombatMove(duration);
MWMechanics::Movement& movement = storage.mMovement;
if(combatMove)
{
timerCombatMove -= duration;
if( timerCombatMove <= 0)
{
timerCombatMove = 0;
movement.mPosition[1] = movement.mPosition[0] = 0;
combatMove = false;
}
}
actorClass.getMovementSettings(actor) = movement;
actorClass.getMovementSettings(actor).mRotation[0] = 0;
@ -242,39 +221,12 @@ namespace MWMechanics
//TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f
float attacksPeriod = 1.0f;
ESM::Weapon::AttackType attackType;
bool& attack = storage.mAttack;
bool& readyToAttack = storage.mReadyToAttack;
float& timerAttack = storage.mTimerAttack;
bool& minMaxAttackDurationInitialised = storage.mMinMaxAttackDurationInitialised;
float (&minMaxAttackDuration)[3][2] = storage.mMinMaxAttackDuration;
if(readyToAttack)
{
if (!minMaxAttackDurationInitialised)
{
// TODO: this must be updated when a different weapon is equipped
getMinMaxAttackDuration(actor, minMaxAttackDuration);
minMaxAttackDurationInitialised = true;
}
if (timerAttack < 0) attack = false;
timerAttack -= duration;
}
else
{
timerAttack = -attacksPeriod;
attack = false;
}
actorClass.getCreatureStats(actor).setAttackingOrSpell(attack);
// update attack state
storage.updateWeaponAttackDurations(actor);
storage.updateAttack(duration, attacksPeriod);
actorClass.getCreatureStats(actor).setAttackingOrSpell(storage.mAttack);
float& actionCooldown = storage.mActionCooldown;
actionCooldown -= duration;
@ -383,38 +335,20 @@ namespace MWMechanics
weapRange = 150.f;
}
float& strength = storage.mStrength;
// start new attack
if(readyToAttack)
bool newAttack = storage.startAttack(distantCombat, weapon, attacksPeriod);
if (newAttack && actor.getClass().isNpc())
{
if(timerAttack <= -attacksPeriod)
//say a provoking combat phrase
const MWWorld::ESMStore &store = world->getStore();
int chance = store.get<ESM::GameSetting>().find("iVoiceAttackOdds")->getInt();
if (OEngine::Misc::Rng::roll0to99() < chance)
{
attack = true; // attack starts just now
if (!distantCombat) attackType = chooseBestAttack(weapon, movement);
else attackType = ESM::Weapon::AT_Chop; // cause it's =0
strength = OEngine::Misc::Rng::rollClosedProbability();
// Note: may be 0 for some animations
timerAttack = minMaxAttackDuration[attackType][0] +
(minMaxAttackDuration[attackType][1] - minMaxAttackDuration[attackType][0]) * strength;
//say a provoking combat phrase
if (actor.getClass().isNpc())
{
const MWWorld::ESMStore &store = world->getStore();
int chance = store.get<ESM::GameSetting>().find("iVoiceAttackOdds")->getInt();
if (OEngine::Misc::Rng::roll0to99() < chance)
{
MWBase::Environment::get().getDialogueManager()->say(actor, "attack");
}
}
MWBase::Environment::get().getDialogueManager()->say(actor, "attack");
}
}
/*
* Some notes on meanings of variables:
*
@ -492,7 +426,7 @@ namespace MWMechanics
if (distantCombat)
{
Ogre::Vector3& lastTargetPos = storage.mLastTargetPos;
Ogre::Vector3 vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, tReaction, weaptype, strength);
Ogre::Vector3 vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, tReaction, weaptype, storage.mStrength);
lastTargetPos = vTargetPos;
movement.mRotation[0] = getXAngleToDir(vAimDir);
movement.mRotation[2] = getZAngleToDir(vAimDir);
@ -507,31 +441,12 @@ namespace MWMechanics
if (followTarget && distToTarget > rangeAttack)
{
//Close-up combat: just run up on target
storage.stopCombatMove();
movement.mPosition[1] = 1;
}
else // (within attack dist)
{
if(movement.mPosition[0] || movement.mPosition[1])
{
timerCombatMove = 0.1f + 0.1f * OEngine::Misc::Rng::rollClosedProbability();
combatMove = true;
}
// only NPCs are smart enough to use dodge movements
else if(actorClass.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2)))
{
//apply sideway movement (kind of dodging) with some probability
if (OEngine::Misc::Rng::rollClosedProbability() < 0.25)
{
movement.mPosition[0] = OEngine::Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f;
timerCombatMove = 0.05f + 0.15f * OEngine::Misc::Rng::rollClosedProbability();
combatMove = true;
}
}
if(distantCombat && distToTarget < rangeAttack/4)
{
movement.mPosition[1] = -1;
}
storage.startCombatMove(actorClass.isNpc(), distantCombat, distToTarget, rangeAttack);
readyToAttack = true;
//only once got in melee combat, actor is allowed to use close-up shortcutting
@ -609,52 +524,24 @@ namespace MWMechanics
}
}
movement.mPosition[1] = 1;
if (readyToAttack)
{
// to stop possible sideway moving after target moved out of attack range
combatMove = true;
timerCombatMove = 0;
storage.stopCombatMove();
readyToAttack = false;
}
readyToAttack = false;
movement.mPosition[1] = 1;
}
if(!isStuck && distToTarget > rangeAttack && !distantCombat)
if (!isStuck && distToTarget > rangeAttack && !distantCombat)
{
//special run attack; it shouldn't affect melee combat tactics
if(actorClass.getMovementSettings(actor).mPosition[1] == 1)
// special run attack; it shouldn't affect melee combat tactics
if (actorClass.getMovementSettings(actor).mPosition[1] == 1)
{
/* check if actor can overcome the distance = distToTarget - attackerWeapRange
less than in time of swinging with weapon (t_swing), then start attacking
*/
float speed1 = actorClass.getSpeed(actor);
float speed2 = target.getClass().getSpeed(target);
if(target.getClass().getMovementSettings(target).mPosition[0] == 0
&& target.getClass().getMovementSettings(target).mPosition[1] == 0)
speed2 = 0;
float s1 = distToTarget - weapRange;
float t = s1/speed1;
float s2 = speed2 * t;
float t_swing =
minMaxAttackDuration[ESM::Weapon::AT_Thrust][0] +
(minMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - minMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * OEngine::Misc::Rng::rollClosedProbability();
if (t + s2/speed1 <= t_swing)
{
readyToAttack = true;
if(timerAttack <= -attacksPeriod)
{
timerAttack = t_swing;
attack = true;
}
}
storage.startRunAttack(actor, target, attacksPeriod, distToTarget, weapRange);
}
}
// NOTE: This section gets updated every tReaction, which is currently hard
// coded at 250ms or 1/4 second
//
// TODO: Add a parameter to vary DURATION_SAME_SPOT?
if((distToTarget > rangeAttack || followTarget) &&
mObstacleCheck.check(actor, tReaction)) // check if evasive action needed
@ -749,187 +636,322 @@ namespace MWMechanics
package.mPackage = combat.release();
sequence.mPackages.push_back(package);
}
void AiCombatStorage::startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack)
{
if (mMovement.mPosition[0] || mMovement.mPosition[1])
{
mTimerCombatMove = 0.1f + 0.1f * OEngine::Misc::Rng::rollClosedProbability();
mCombatMove = true;
}
// only NPCs are smart enough to use dodge movements
else if (isNpc && (!isDistantCombat || (isDistantCombat && distToTarget < rangeAttack/2)))
{
//apply sideway movement (kind of dodging) with some probability
if (OEngine::Misc::Rng::rollClosedProbability() < 0.25)
{
mMovement.mPosition[0] = OEngine::Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f;
mTimerCombatMove = 0.05f + 0.15f * OEngine::Misc::Rng::rollClosedProbability();
mCombatMove = true;
}
}
if (isDistantCombat && distToTarget < rangeAttack/4)
{
mMovement.mPosition[1] = -1;
}
}
void AiCombatStorage::updateCombatMove(float duration)
{
if (!mCombatMove) return;
mTimerCombatMove -= duration;
if (mTimerCombatMove <= 0)
{
stopCombatMove();
}
}
void AiCombatStorage::stopCombatMove()
{
mCombatMove = false;
mTimerCombatMove = 0;
mMovement.mPosition[1] = mMovement.mPosition[0] = 0;
}
void AiCombatStorage::updateWeaponAttackDurations(const MWWorld::Ptr& actor)
{
if (mReadyToAttack && !mMinMaxAttackDurationInitialised)
{
// TODO: this must be updated when a different weapon is equipped
getMinMaxAttackDuration(actor, mMinMaxAttackDuration);
mMinMaxAttackDurationInitialised = true;
}
}
void AiCombatStorage::updateAttack(float duration, float attacksPeriod)
{
if (mReadyToAttack)
{
if (mTimerAttack < 0) mAttack = false;
mTimerAttack -= duration;
}
else
{
mTimerAttack = -attacksPeriod;
mAttack = false;
}
}
bool AiCombatStorage::startAttack(bool isDistantCombat, const ESM::Weapon* weapon, float attacksPeriod)
{
if (!mReadyToAttack || mTimerAttack > -attacksPeriod)
return false;
// start new attack
mAttack = true;
ESM::Weapon::AttackType attackType;
if (!isDistantCombat)
attackType = chooseBestAttack(weapon, mMovement);
else
attackType = ESM::Weapon::AT_Chop; // cause it's =0
mStrength = OEngine::Misc::Rng::rollClosedProbability();
// Note: may be 0 for some animations
mTimerAttack = mMinMaxAttackDuration[attackType][0] +
(mMinMaxAttackDuration[attackType][1] - mMinMaxAttackDuration[attackType][0]) * mStrength;
return true;
}
void AiCombatStorage::startRunAttack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float attacksPeriod, float distToTarget, float weapRange)
{
float actorSpeed = actor.getClass().getSpeed(actor);
float targetSpeed = target.getClass().getSpeed(target);
if (target.getClass().getMovementSettings(target).mPosition[0] == 0
&& target.getClass().getMovementSettings(target).mPosition[1] == 0)
targetSpeed = 0;
float distToOvercome = distToTarget - weapRange;
float t_toOvercome = distToOvercome/actorSpeed;
float distTargetWillOvercome = targetSpeed * t_toOvercome;
float t_weap_swing = mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0] +
(mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * OEngine::Misc::Rng::rollClosedProbability();
if (t_toOvercome + distTargetWillOvercome/actorSpeed <= t_weap_swing)
{
mReadyToAttack = true;
if (mTimerAttack <= -attacksPeriod)
{
mTimerAttack = t_weap_swing;
mAttack = true;
}
}
}
}
namespace
{
ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement)
{
ESM::Weapon::AttackType attackType;
if (weapon == NULL)
ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement)
{
//hand-to-hand deal equal damage for each type
float roll = OEngine::Misc::Rng::rollClosedProbability();
if(roll <= 0.333f) //side punch
ESM::Weapon::AttackType attackType;
if (weapon == NULL)
{
movement.mPosition[0] = OEngine::Misc::Rng::rollClosedProbability() ? 1.0f : -1.0f;
movement.mPosition[1] = 0;
attackType = ESM::Weapon::AT_Slash;
}
else if(roll <= 0.666f) //forward punch
{
movement.mPosition[1] = 1;
attackType = ESM::Weapon::AT_Thrust;
//hand-to-hand deal equal damage for each type
float roll = OEngine::Misc::Rng::rollClosedProbability();
if(roll <= 0.333f) //side punch
{
movement.mPosition[0] = OEngine::Misc::Rng::rollClosedProbability() ? 1.0f : -1.0f;
movement.mPosition[1] = 0;
attackType = ESM::Weapon::AT_Slash;
}
else if(roll <= 0.666f) //forward punch
{
movement.mPosition[1] = 1;
attackType = ESM::Weapon::AT_Thrust;
}
else
{
movement.mPosition[1] = movement.mPosition[0] = 0;
attackType = ESM::Weapon::AT_Chop;
}
}
else
{
movement.mPosition[1] = movement.mPosition[0] = 0;
attackType = ESM::Weapon::AT_Chop;
//the more damage attackType deals the more probability it has
int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2;
int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2;
int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2;
float total = static_cast<float>(slash + chop + thrust);
float roll = OEngine::Misc::Rng::rollClosedProbability();
if(roll <= (slash/total))
{
movement.mPosition[0] = (OEngine::Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f;
movement.mPosition[1] = 0;
attackType = ESM::Weapon::AT_Slash;
}
else if(roll <= (slash + (thrust/total)))
{
movement.mPosition[1] = 1;
attackType = ESM::Weapon::AT_Thrust;
}
else
{
movement.mPosition[1] = movement.mPosition[0] = 0;
attackType = ESM::Weapon::AT_Chop;
}
}
return attackType;
}
else
void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2])
{
//the more damage attackType deals the more probability it has
int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2;
int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2;
int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2;
float total = static_cast<float>(slash + chop + thrust);
float roll = OEngine::Misc::Rng::rollClosedProbability();
if(roll <= (slash/total))
if (!actor.getClass().hasInventoryStore(actor)) // creatures
{
movement.mPosition[0] = (OEngine::Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f;
movement.mPosition[1] = 0;
attackType = ESM::Weapon::AT_Slash;
fMinMaxDurations[0][0] = fMinMaxDurations[0][1] = 0.1f;
fMinMaxDurations[1][0] = fMinMaxDurations[1][1] = 0.1f;
fMinMaxDurations[2][0] = fMinMaxDurations[2][1] = 0.1f;
return;
}
else if(roll <= (slash + (thrust/total)))
// get weapon information: type and speed
const ESM::Weapon *weapon = NULL;
MWMechanics::WeaponType weaptype = MWMechanics::WeapType_None;
MWWorld::ContainerStoreIterator weaponSlot =
MWMechanics::getActiveWeapon(actor.getClass().getCreatureStats(actor), actor.getClass().getInventoryStore(actor), &weaptype);
float weapSpeed;
if (weaptype != MWMechanics::WeapType_HandToHand
&& weaptype != MWMechanics::WeapType_Spell
&& weaptype != MWMechanics::WeapType_None)
{
movement.mPosition[1] = 1;
attackType = ESM::Weapon::AT_Thrust;
weapon = weaponSlot->get<ESM::Weapon>()->mBase;
weapSpeed = weapon->mData.mSpeed;
}
else weapSpeed = 1.0f;
MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(actor);
std::string weapGroup;
MWMechanics::getWeaponGroup(weaptype, weapGroup);
weapGroup = weapGroup + ": ";
bool bRangedWeap = (weaptype >= MWMechanics::WeapType_BowAndArrow && weaptype <= MWMechanics::WeapType_Thrown);
const char *attackType[] = {"chop ", "slash ", "thrust ", "shoot "};
std::string textKey = "start";
std::string textKey2;
// get durations for each attack type
for (int i = 0; i < (bRangedWeap ? 1 : 3); i++)
{
float start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey);
if (start1 < 0)
{
fMinMaxDurations[i][0] = fMinMaxDurations[i][1] = 0.1f;
continue;
}
textKey2 = "min attack";
float start2 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2);
fMinMaxDurations[i][0] = (start2 - start1) / weapSpeed;
textKey2 = "max attack";
start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2);
fMinMaxDurations[i][1] = fMinMaxDurations[i][0] + (start1 - start2) / weapSpeed;
}
}
Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos,
float duration, int weapType, float strength)
{
float projSpeed;
// get projectile speed (depending on weapon type)
if (weapType == ESM::Weapon::MarksmanThrown)
{
static float fThrownWeaponMinSpeed =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fThrownWeaponMinSpeed")->getFloat();
static float fThrownWeaponMaxSpeed =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fThrownWeaponMaxSpeed")->getFloat();
projSpeed =
fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * strength;
}
else
{
movement.mPosition[1] = movement.mPosition[0] = 0;
attackType = ESM::Weapon::AT_Chop;
static float fProjectileMinSpeed =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fProjectileMinSpeed")->getFloat();
static float fProjectileMaxSpeed =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fProjectileMaxSpeed")->getFloat();
projSpeed =
fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * strength;
}
// idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same
Ogre::Vector3 vActorPos = Ogre::Vector3(actor.getRefData().getPosition().pos);
Ogre::Vector3 vTargetPos = Ogre::Vector3(target.getRefData().getPosition().pos);
Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos;
float distToTarget = vDirToTarget.length();
Ogre::Vector3 vTargetMoveDir = vTargetPos - vLastTargetPos;
vTargetMoveDir /= duration; // |vTargetMoveDir| is target real speed in units/sec now
Ogre::Vector3 vPerpToDir = vDirToTarget.crossProduct(Ogre::Vector3::UNIT_Z);
float velPerp = vTargetMoveDir.dotProduct(vPerpToDir.normalisedCopy());
float velDir = vTargetMoveDir.dotProduct(vDirToTarget.normalisedCopy());
// time to collision between target and projectile
float t_collision;
float projVelDirSquared = projSpeed * projSpeed - velPerp * velPerp;
float projDistDiff = vDirToTarget.dotProduct(vTargetMoveDir.normalisedCopy());
projDistDiff = sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff);
if (projVelDirSquared > 0)
t_collision = projDistDiff / (sqrt(projVelDirSquared) - velDir);
else t_collision = 0; // speed of projectile is not enough to reach moving target
return vTargetPos + vTargetMoveDir * t_collision - vActorPos;
}
return attackType;
}
void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2])
{
if (!actor.getClass().hasInventoryStore(actor)) // creatures
bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY)
{
fMinMaxDurations[0][0] = fMinMaxDurations[0][1] = 0.1f;
fMinMaxDurations[1][0] = fMinMaxDurations[1][1] = 0.1f;
fMinMaxDurations[2][0] = fMinMaxDurations[2][1] = 0.1f;
return;
}
// get weapon information: type and speed
const ESM::Weapon *weapon = NULL;
MWMechanics::WeaponType weaptype = MWMechanics::WeapType_None;
MWWorld::ContainerStoreIterator weaponSlot =
MWMechanics::getActiveWeapon(actor.getClass().getCreatureStats(actor), actor.getClass().getInventoryStore(actor), &weaptype);
float weapSpeed;
if (weaptype != MWMechanics::WeapType_HandToHand
&& weaptype != MWMechanics::WeapType_Spell
&& weaptype != MWMechanics::WeapType_None)
{
weapon = weaponSlot->get<ESM::Weapon>()->mBase;
weapSpeed = weapon->mData.mSpeed;
}
else weapSpeed = 1.0f;
MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(actor);
std::string weapGroup;
MWMechanics::getWeaponGroup(weaptype, weapGroup);
weapGroup = weapGroup + ": ";
bool bRangedWeap = (weaptype >= MWMechanics::WeapType_BowAndArrow && weaptype <= MWMechanics::WeapType_Thrown);
const char *attackType[] = {"chop ", "slash ", "thrust ", "shoot "};
std::string textKey = "start";
std::string textKey2;
// get durations for each attack type
for (int i = 0; i < (bRangedWeap ? 1 : 3); i++)
{
float start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey);
if (start1 < 0)
if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z - to.z) <= PATHFIND_Z_REACH)
{
fMinMaxDurations[i][0] = fMinMaxDurations[i][1] = 0.1f;
continue;
Ogre::Vector3 dir = to - from;
dir.z = 0;
dir.normalise();
float verticalOffset = 200; // instead of '200' here we want the height of the actor
Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset;
// cast up-down ray and find height in world space of hit
float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1);
if(std::abs(from.z - h) <= PATHFIND_Z_REACH)
return true;
}
textKey2 = "min attack";
float start2 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2);
fMinMaxDurations[i][0] = (start2 - start1) / weapSpeed;
textKey2 = "max attack";
start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2);
fMinMaxDurations[i][1] = fMinMaxDurations[i][0] + (start1 - start2) / weapSpeed;
return false;
}
}
Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos,
float duration, int weapType, float strength)
{
float projSpeed;
// get projectile speed (depending on weapon type)
if (weapType == ESM::Weapon::MarksmanThrown)
{
static float fThrownWeaponMinSpeed =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fThrownWeaponMinSpeed")->getFloat();
static float fThrownWeaponMaxSpeed =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fThrownWeaponMaxSpeed")->getFloat();
projSpeed =
fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * strength;
}
else
{
static float fProjectileMinSpeed =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fProjectileMinSpeed")->getFloat();
static float fProjectileMaxSpeed =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fProjectileMaxSpeed")->getFloat();
projSpeed =
fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * strength;
}
// idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same
Ogre::Vector3 vActorPos = Ogre::Vector3(actor.getRefData().getPosition().pos);
Ogre::Vector3 vTargetPos = Ogre::Vector3(target.getRefData().getPosition().pos);
Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos;
float distToTarget = vDirToTarget.length();
Ogre::Vector3 vTargetMoveDir = vTargetPos - vLastTargetPos;
vTargetMoveDir /= duration; // |vTargetMoveDir| is target real speed in units/sec now
Ogre::Vector3 vPerpToDir = vDirToTarget.crossProduct(Ogre::Vector3::UNIT_Z);
float velPerp = vTargetMoveDir.dotProduct(vPerpToDir.normalisedCopy());
float velDir = vTargetMoveDir.dotProduct(vDirToTarget.normalisedCopy());
// time to collision between target and projectile
float t_collision;
float projVelDirSquared = projSpeed * projSpeed - velPerp * velPerp;
float projDistDiff = vDirToTarget.dotProduct(vTargetMoveDir.normalisedCopy());
projDistDiff = sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff);
if (projVelDirSquared > 0)
t_collision = projDistDiff / (sqrt(projVelDirSquared) - velDir);
else t_collision = 0; // speed of projectile is not enough to reach moving target
return vTargetPos + vTargetMoveDir * t_collision - vActorPos;
}
}