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:
parent
7b207a7954
commit
1d4be08f6e
1 changed files with 352 additions and 330 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue