1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-11-03 23:56:43 +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
@ -123,7 +105,19 @@ namespace MWMechanics
mMinMaxAttackDurationInitialised(false),
mLastTargetPos(0,0,0),
mLastActorPos(0,0,0),
mMovement(){}
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) :
@ -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,27 +335,12 @@ 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)
{
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)
@ -411,9 +348,6 @@ namespace MWMechanics
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;
}
movement.mPosition[1] = 1;
}
if (!isStuck && distToTarget > rangeAttack && !distantCombat)
{
// 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,12 +636,128 @@ 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;
@ -932,4 +935,23 @@ Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr
return vTargetPos + vTargetMoveDir * t_collision - vActorPos;
}
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;
}
}