forked from mirror/openmw-tes3mp
aiming to moving target in ranged combat ai
1) Taking into account target move vector and speed. However aiming is not ideal, since attack strength can't be controlled directly. I did achieve almost 100% accuracy updating it everyframe but then thought it would be unfair, cause AI should mimic human targetting. 2) Also added in this commit func to measure real attack durations for weapon.
This commit is contained in:
parent
8fa7fcdbee
commit
67abc60264
5 changed files with 278 additions and 100 deletions
|
@ -1,8 +1,6 @@
|
||||||
#include "aicombat.hpp"
|
#include "aicombat.hpp"
|
||||||
|
|
||||||
#include <OgreMath.h>
|
#include <OgreMath.h>
|
||||||
#include <OgreVector3.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/timestamp.hpp"
|
#include "../mwworld/timestamp.hpp"
|
||||||
|
@ -14,6 +12,8 @@
|
||||||
#include "../mwbase/mechanicsmanager.hpp"
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
#include "../mwbase/dialoguemanager.hpp"
|
#include "../mwbase/dialoguemanager.hpp"
|
||||||
|
|
||||||
|
#include "../mwrender/animation.hpp"
|
||||||
|
|
||||||
|
|
||||||
#include "creaturestats.hpp"
|
#include "creaturestats.hpp"
|
||||||
#include "steering.hpp"
|
#include "steering.hpp"
|
||||||
|
@ -30,7 +30,12 @@ namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
//chooses an attack depending on probability to avoid uniformity
|
//chooses an attack depending on probability to avoid uniformity
|
||||||
void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement);
|
ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement);
|
||||||
|
|
||||||
|
void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2]);
|
||||||
|
|
||||||
|
Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos,
|
||||||
|
float duration, int weapType, float strength);
|
||||||
|
|
||||||
float getZAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f)
|
float getZAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f)
|
||||||
{
|
{
|
||||||
|
@ -76,18 +81,20 @@ namespace
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
static const float MAX_ATTACK_DURATION = 0.35f;
|
|
||||||
static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander
|
static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander
|
||||||
// NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp
|
// NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp
|
||||||
|
|
||||||
AiCombat::AiCombat(const MWWorld::Ptr& actor) :
|
AiCombat::AiCombat(const MWWorld::Ptr& actor) :
|
||||||
mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()),
|
mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()),
|
||||||
|
mLastTargetPos(actor.getRefData().getPosition().pos),
|
||||||
mTimerAttack(0),
|
mTimerAttack(0),
|
||||||
mTimerReact(0),
|
mTimerReact(0),
|
||||||
mTimerCombatMove(0),
|
mTimerCombatMove(0),
|
||||||
mFollowTarget(false),
|
mFollowTarget(false),
|
||||||
mReadyToAttack(false),
|
mReadyToAttack(false),
|
||||||
mAttack(false),
|
mAttack(false),
|
||||||
|
mStrength(0),
|
||||||
|
mMinMaxAttackDuration(),
|
||||||
mCombatMove(false),
|
mCombatMove(false),
|
||||||
mMovement(),
|
mMovement(),
|
||||||
mForceNoShortcut(false),
|
mForceNoShortcut(false),
|
||||||
|
@ -158,9 +165,9 @@ namespace MWMechanics
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!actor.getClass().isNpc() && target == MWBase::Environment::get().getWorld()->getPlayerPtr() &&
|
if (!actor.getClass().isNpc() && target == MWBase::Environment::get().getWorld()->getPlayerPtr() &&
|
||||||
(actor.getClass().canSwim(actor) && !actor.getClass().canWalk(actor) // pure water creature
|
(actor.getClass().canSwim(actor) && !actor.getClass().canWalk(actor) // 1. pure water creature and Player moved out of water
|
||||||
&& !MWBase::Environment::get().getWorld()->isSwimming(target)) // Player moved out of water
|
&& !MWBase::Environment::get().getWorld()->isSwimming(target))
|
||||||
|| (!actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(target))) // creature can't swim to Player
|
|| (!actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(target))) // 2. creature can't swim to Player
|
||||||
{
|
{
|
||||||
actor.getClass().getCreatureStats(actor).setHostile(false);
|
actor.getClass().getCreatureStats(actor).setHostile(false);
|
||||||
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false);
|
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false);
|
||||||
|
@ -194,6 +201,27 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
mTimerAttack -= duration;
|
mTimerAttack -= duration;
|
||||||
|
|
||||||
|
//TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f
|
||||||
|
float attacksPeriod = 1.0f;
|
||||||
|
|
||||||
|
ESM::Weapon::AttackType attackType;
|
||||||
|
|
||||||
|
if(mReadyToAttack)
|
||||||
|
{
|
||||||
|
if (mMinMaxAttackDuration[0][0] == 0)
|
||||||
|
{
|
||||||
|
getMinMaxAttackDuration(actor, mMinMaxAttackDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mTimerAttack <= 0) mAttack = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mTimerAttack = -attacksPeriod;
|
||||||
|
mAttack = false;
|
||||||
|
}
|
||||||
|
|
||||||
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack);
|
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack);
|
||||||
|
|
||||||
float tReaction = 0.25f;
|
float tReaction = 0.25f;
|
||||||
|
@ -213,40 +241,6 @@ namespace MWMechanics
|
||||||
mCell = actor.getCell();
|
mCell = actor.getCell();
|
||||||
}
|
}
|
||||||
|
|
||||||
//actual attacking logic
|
|
||||||
//TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f
|
|
||||||
float attacksPeriod = 1.0f;
|
|
||||||
if(mReadyToAttack)
|
|
||||||
{
|
|
||||||
if(mTimerAttack <= -attacksPeriod)
|
|
||||||
{
|
|
||||||
//TODO: should depend on time between 'start' to 'min attack'
|
|
||||||
//for better controlling of NPCs' attack strength.
|
|
||||||
//Also it seems that this time is different for slash/thrust/chop
|
|
||||||
mTimerAttack = MAX_ATTACK_DURATION * static_cast<float>(rand())/RAND_MAX;
|
|
||||||
mAttack = true;
|
|
||||||
|
|
||||||
//say a provoking combat phrase
|
|
||||||
if (actor.getClass().isNpc())
|
|
||||||
{
|
|
||||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
|
||||||
int chance = store.get<ESM::GameSetting>().find("iVoiceAttackOdds")->getInt();
|
|
||||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
|
||||||
if (roll < chance)
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getDialogueManager()->say(actor, "attack");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (mTimerAttack <= 0)
|
|
||||||
mAttack = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mTimerAttack = -attacksPeriod;
|
|
||||||
mAttack = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MWWorld::Class &actorCls = actor.getClass();
|
const MWWorld::Class &actorCls = actor.getClass();
|
||||||
const ESM::Weapon *weapon = NULL;
|
const ESM::Weapon *weapon = NULL;
|
||||||
MWMechanics::WeaponType weaptype;
|
MWMechanics::WeaponType weaptype;
|
||||||
|
@ -270,9 +264,9 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (weaptype == WeapType_HandToHand)
|
if (weaptype == WeapType_HandToHand)
|
||||||
{
|
{
|
||||||
const MWWorld::Store<ESM::GameSetting> &gmst =
|
static float fHandToHandReach =
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fHandToHandReach")->getFloat();
|
||||||
weapRange = gmst.find("fHandToHandReach")->getFloat();
|
weapRange = fHandToHandReach;
|
||||||
}
|
}
|
||||||
else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell)
|
else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell)
|
||||||
{
|
{
|
||||||
|
@ -289,6 +283,49 @@ namespace MWMechanics
|
||||||
weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit)
|
weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float rangeAttack;
|
||||||
|
float rangeFollow;
|
||||||
|
bool distantCombat = false;
|
||||||
|
if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown)
|
||||||
|
{
|
||||||
|
rangeAttack = 1000; // TODO: should depend on archer skill
|
||||||
|
rangeFollow = 0; // not needed in ranged combat
|
||||||
|
distantCombat = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rangeAttack = weapRange;
|
||||||
|
rangeFollow = 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start new attack
|
||||||
|
if(mReadyToAttack)
|
||||||
|
{
|
||||||
|
if(mTimerAttack <= -attacksPeriod)
|
||||||
|
{
|
||||||
|
mAttack = true; // attack starts just now
|
||||||
|
|
||||||
|
if (!distantCombat) attackType = chooseBestAttack(weapon, mMovement);
|
||||||
|
else attackType = ESM::Weapon::AT_Chop; // cause it's =0
|
||||||
|
|
||||||
|
mStrength = static_cast<float>(rand()) / RAND_MAX;
|
||||||
|
mTimerAttack = mMinMaxAttackDuration[attackType][0] +
|
||||||
|
(mMinMaxAttackDuration[attackType][1] - mMinMaxAttackDuration[attackType][0]) * mStrength;
|
||||||
|
|
||||||
|
//say a provoking combat phrase
|
||||||
|
if (actor.getClass().isNpc())
|
||||||
|
{
|
||||||
|
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
|
int chance = store.get<ESM::GameSetting>().find("iVoiceAttackOdds")->getInt();
|
||||||
|
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||||
|
if (roll < chance)
|
||||||
|
{
|
||||||
|
MWBase::Environment::get().getDialogueManager()->say(actor, "attack");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Some notes on meanings of variables:
|
* Some notes on meanings of variables:
|
||||||
|
@ -319,21 +356,6 @@ namespace MWMechanics
|
||||||
* target even if LOS is not achieved)
|
* target even if LOS is not achieved)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
float rangeAttack;
|
|
||||||
float rangeFollow;
|
|
||||||
bool distantCombat = false;
|
|
||||||
if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown)
|
|
||||||
{
|
|
||||||
rangeAttack = 1000; // TODO: should depend on archer skill
|
|
||||||
rangeFollow = 0; // not needed in ranged combat
|
|
||||||
distantCombat = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rangeAttack = weapRange;
|
|
||||||
rangeFollow = 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESM::Position pos = actor.getRefData().getPosition();
|
ESM::Position pos = actor.getRefData().getPosition();
|
||||||
Ogre::Vector3 vActorPos(pos.pos);
|
Ogre::Vector3 vActorPos(pos.pos);
|
||||||
Ogre::Vector3 vTargetPos(target.getRefData().getPosition().pos);
|
Ogre::Vector3 vTargetPos(target.getRefData().getPosition().pos);
|
||||||
|
@ -342,40 +364,46 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool isStuck = false;
|
bool isStuck = false;
|
||||||
float speed = 0.0f;
|
float speed = 0.0f;
|
||||||
if(mMovement.mPosition[1] && (Ogre::Vector3(mLastPos.pos) - vActorPos).length() < (speed = actorCls.getSpeed(actor)) * tReaction / 2)
|
if(mMovement.mPosition[1] && (mLastActorPos - vActorPos).length() < (speed = actorCls.getSpeed(actor)) * tReaction / 2)
|
||||||
isStuck = true;
|
isStuck = true;
|
||||||
|
|
||||||
mLastPos = pos;
|
mLastActorPos = vActorPos;
|
||||||
|
|
||||||
// check if actor can move along z-axis
|
// check if actor can move along z-axis
|
||||||
bool canMoveByZ = (actorCls.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|
bool canMoveByZ = (actorCls.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|
||||||
|| MWBase::Environment::get().getWorld()->isFlying(actor);
|
|| MWBase::Environment::get().getWorld()->isFlying(actor);
|
||||||
|
|
||||||
// determine vertical angle to target
|
// (within attack dist) || (not quite attack dist while following)
|
||||||
// if actor can move along z-axis it will control movement dir
|
|
||||||
// if can't - it will control correct aiming
|
|
||||||
mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget);
|
|
||||||
|
|
||||||
// (within strike dist) || (not quite strike dist while following)
|
|
||||||
if(distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck) )
|
if(distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck) )
|
||||||
{
|
{
|
||||||
//Melee and Close-up combat
|
//Melee and Close-up combat
|
||||||
|
|
||||||
// if we preserve dir.z then horizontal angle can be inaccurate
|
// getXAngleToDir determines vertical angle to target:
|
||||||
mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0));
|
// if actor can move along z-axis it will control movement dir
|
||||||
|
// if can't - it will control correct aiming.
|
||||||
|
// note: in getZAngleToDir if we preserve dir.z then horizontal angle can be inaccurate
|
||||||
|
if (distantCombat)
|
||||||
|
{
|
||||||
|
Ogre::Vector3 vAimDir = AimDirToMovingTarget(actor, target, mLastTargetPos, tReaction, weaptype, mStrength);
|
||||||
|
mLastTargetPos = vTargetPos;
|
||||||
|
mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
||||||
|
mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vAimDir.x, vAimDir.y, 0));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget);
|
||||||
|
mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0));
|
||||||
|
}
|
||||||
|
|
||||||
// (not quite strike dist while following)
|
// (not quite attack dist while following)
|
||||||
if (mFollowTarget && distToTarget > rangeAttack)
|
if (mFollowTarget && distToTarget > rangeAttack)
|
||||||
{
|
{
|
||||||
//Close-up combat: just run up on target
|
//Close-up combat: just run up on target
|
||||||
mMovement.mPosition[1] = 1;
|
mMovement.mPosition[1] = 1;
|
||||||
}
|
}
|
||||||
else // (within strike dist)
|
else // (within attack dist)
|
||||||
{
|
{
|
||||||
mMovement.mPosition[1] = 0;
|
if (!mAttack) mMovement.mPosition[1] = 0;
|
||||||
|
|
||||||
// set slash/thrust/chop attack
|
|
||||||
if (mAttack && !distantCombat) chooseBestAttack(weapon, mMovement);
|
|
||||||
|
|
||||||
if(mMovement.mPosition[0] || mMovement.mPosition[1])
|
if(mMovement.mPosition[0] || mMovement.mPosition[1])
|
||||||
{
|
{
|
||||||
|
@ -479,9 +507,9 @@ namespace MWMechanics
|
||||||
//special run attack; it shouldn't affect melee combat tactics
|
//special run attack; it shouldn't affect melee combat tactics
|
||||||
if(actorCls.getMovementSettings(actor).mPosition[1] == 1)
|
if(actorCls.getMovementSettings(actor).mPosition[1] == 1)
|
||||||
{
|
{
|
||||||
//check if actor can overcome the distance = distToTarget - attackerWeapRange
|
/* check if actor can overcome the distance = distToTarget - attackerWeapRange
|
||||||
//less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing)
|
less than in time of swinging with weapon (t_swing), then start attacking
|
||||||
//then start attacking
|
*/
|
||||||
float speed1 = actorCls.getSpeed(actor);
|
float speed1 = actorCls.getSpeed(actor);
|
||||||
float speed2 = target.getClass().getSpeed(target);
|
float speed2 = target.getClass().getSpeed(target);
|
||||||
if(target.getClass().getMovementSettings(target).mPosition[0] == 0
|
if(target.getClass().getMovementSettings(target).mPosition[0] == 0
|
||||||
|
@ -491,13 +519,16 @@ namespace MWMechanics
|
||||||
float s1 = distToTarget - weapRange;
|
float s1 = distToTarget - weapRange;
|
||||||
float t = s1/speed1;
|
float t = s1/speed1;
|
||||||
float s2 = speed2 * t;
|
float s2 = speed2 * t;
|
||||||
float t_swing = (MAX_ATTACK_DURATION/2) / weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags
|
float t_swing =
|
||||||
|
mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0] +
|
||||||
|
(mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * static_cast<float>(rand()) / RAND_MAX;
|
||||||
|
|
||||||
if (t + s2/speed1 <= t_swing)
|
if (t + s2/speed1 <= t_swing)
|
||||||
{
|
{
|
||||||
mReadyToAttack = true;
|
mReadyToAttack = true;
|
||||||
if(mTimerAttack <= -attacksPeriod)
|
if(mTimerAttack <= -attacksPeriod)
|
||||||
{
|
{
|
||||||
mTimerAttack = MAX_ATTACK_DURATION * static_cast<float>(rand())/RAND_MAX;
|
mTimerAttack = t_swing;
|
||||||
mAttack = true;
|
mAttack = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -650,8 +681,10 @@ namespace MWMechanics
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement)
|
ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement)
|
||||||
{
|
{
|
||||||
|
ESM::Weapon::AttackType attackType;
|
||||||
|
|
||||||
if (weapon == NULL)
|
if (weapon == NULL)
|
||||||
{
|
{
|
||||||
//hand-to-hand deal equal damage for each type
|
//hand-to-hand deal equal damage for each type
|
||||||
|
@ -660,34 +693,161 @@ void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement
|
||||||
{
|
{
|
||||||
movement.mPosition[0] = (static_cast<float>(rand())/RAND_MAX < 0.5f)? 1: -1;
|
movement.mPosition[0] = (static_cast<float>(rand())/RAND_MAX < 0.5f)? 1: -1;
|
||||||
movement.mPosition[1] = 0;
|
movement.mPosition[1] = 0;
|
||||||
|
attackType = ESM::Weapon::AT_Slash;
|
||||||
}
|
}
|
||||||
else if(roll <= 0.666f) //forward punch
|
else if(roll <= 0.666f) //forward punch
|
||||||
|
{
|
||||||
movement.mPosition[1] = 1;
|
movement.mPosition[1] = 1;
|
||||||
|
attackType = ESM::Weapon::AT_Thrust;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
movement.mPosition[1] = movement.mPosition[0] = 0;
|
movement.mPosition[1] = movement.mPosition[0] = 0;
|
||||||
|
attackType = ESM::Weapon::AT_Chop;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//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 = slash + chop + thrust;
|
||||||
|
|
||||||
|
float roll = static_cast<float>(rand())/RAND_MAX;
|
||||||
|
if(roll <= static_cast<float>(slash)/total)
|
||||||
|
{
|
||||||
|
movement.mPosition[0] = (static_cast<float>(rand())/RAND_MAX < 0.5f)? 1: -1;
|
||||||
|
movement.mPosition[1] = 0;
|
||||||
|
attackType = ESM::Weapon::AT_Slash;
|
||||||
|
}
|
||||||
|
else if(roll <= (static_cast<float>(slash) + static_cast<float>(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2])
|
||||||
|
{
|
||||||
|
if (!actor.getClass().hasInventoryStore(actor)) // creatures
|
||||||
|
{
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//the more damage attackType deals the more probability it has
|
// get weapon information: type and speed
|
||||||
int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2;
|
const ESM::Weapon *weapon = NULL;
|
||||||
int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2;
|
MWMechanics::WeaponType weaptype;
|
||||||
int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2;
|
|
||||||
|
|
||||||
float total = slash + chop + thrust;
|
MWWorld::ContainerStoreIterator weaponSlot =
|
||||||
|
MWMechanics::getActiveWeapon(actor.getClass().getCreatureStats(actor), actor.getClass().getInventoryStore(actor), &weaptype);
|
||||||
|
|
||||||
float roll = static_cast<float>(rand())/RAND_MAX;
|
float weapSpeed;
|
||||||
if(roll <= static_cast<float>(slash)/total)
|
if (weaptype != MWMechanics::WeapType_HandToHand)
|
||||||
{
|
{
|
||||||
movement.mPosition[0] = (static_cast<float>(rand())/RAND_MAX < 0.5f)? 1: -1;
|
weapon = weaponSlot->get<ESM::Weapon>()->mBase;
|
||||||
movement.mPosition[1] = 0;
|
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->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey, false);
|
||||||
|
|
||||||
|
textKey2 = "min attack";
|
||||||
|
float start2 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2, false);
|
||||||
|
|
||||||
|
fMinMaxDurations[i][0] = (start2 - start1) / weapSpeed;
|
||||||
|
|
||||||
|
textKey2 = "max attack";
|
||||||
|
start1 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2, false);
|
||||||
|
|
||||||
|
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 if(roll <= (static_cast<float>(slash) + static_cast<float>(thrust))/total)
|
|
||||||
movement.mPosition[1] = 1;
|
|
||||||
else
|
else
|
||||||
movement.mPosition[1] = movement.mPosition[0] = 0;
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
#include "movement.hpp"
|
#include "movement.hpp"
|
||||||
#include "obstacle.hpp"
|
#include "obstacle.hpp"
|
||||||
|
|
||||||
|
#include <OgreVector3.h>
|
||||||
|
|
||||||
#include "../mwworld/cellstore.hpp" // for Doors
|
#include "../mwworld/cellstore.hpp" // for Doors
|
||||||
|
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
@ -48,12 +50,17 @@ namespace MWMechanics
|
||||||
bool mCombatMove;
|
bool mCombatMove;
|
||||||
bool mBackOffDoor;
|
bool mBackOffDoor;
|
||||||
|
|
||||||
|
float mStrength; // this is actually make sense only in ranged combat
|
||||||
|
float mMinMaxAttackDuration[3][2]; // slash, thrust, chop has different durations
|
||||||
|
|
||||||
bool mForceNoShortcut;
|
bool mForceNoShortcut;
|
||||||
ESM::Position mShortcutFailPos;
|
ESM::Position mShortcutFailPos;
|
||||||
|
|
||||||
ESM::Position mLastPos;
|
Ogre::Vector3 mLastActorPos;
|
||||||
MWMechanics::Movement mMovement;
|
MWMechanics::Movement mMovement;
|
||||||
|
|
||||||
int mTargetActorId;
|
int mTargetActorId;
|
||||||
|
Ogre::Vector3 mLastTargetPos;
|
||||||
|
|
||||||
const MWWorld::CellStore* mCell;
|
const MWWorld::CellStore* mCell;
|
||||||
ObstacleCheck mObstacleCheck;
|
ObstacleCheck mObstacleCheck;
|
||||||
|
|
|
@ -896,15 +896,26 @@ bool Animation::getInfo(const std::string &groupname, float *complete, float *sp
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Animation::getStartTime(const std::string &groupname) const
|
float Animation::getStartTime(const std::string &groupname, bool onlyGroup) const
|
||||||
{
|
{
|
||||||
AnimSourceList::const_iterator iter(mAnimSources.begin());
|
AnimSourceList::const_iterator iter(mAnimSources.begin());
|
||||||
for(;iter != mAnimSources.end();iter++)
|
for(;iter != mAnimSources.end();iter++)
|
||||||
{
|
{
|
||||||
const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys;
|
const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys;
|
||||||
NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname);
|
if (onlyGroup)
|
||||||
if(found != keys.end())
|
{
|
||||||
return found->first;
|
NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname);
|
||||||
|
if(found != keys.end())
|
||||||
|
return found->first;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for(NifOgre::TextKeyMap::const_iterator iter(keys.begin()); iter != keys.end(); ++iter)
|
||||||
|
{
|
||||||
|
if(iter->second.compare(0, groupname.size(), groupname) == 0)
|
||||||
|
return iter->first;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return -1.f;
|
return -1.f;
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,7 +275,7 @@ public:
|
||||||
bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const;
|
bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const;
|
||||||
|
|
||||||
/// Get the absolute position in the animation track of the first text key with the given group.
|
/// Get the absolute position in the animation track of the first text key with the given group.
|
||||||
float getStartTime(const std::string &groupname) const;
|
float getStartTime(const std::string &groupname, bool onlyGroup) const;
|
||||||
|
|
||||||
/// Get the current absolute position in the animation track for the animation that is currently playing from the given group.
|
/// Get the current absolute position in the animation track for the animation that is currently playing from the given group.
|
||||||
float getCurrentTime(const std::string& groupname) const;
|
float getCurrentTime(const std::string& groupname) const;
|
||||||
|
|
|
@ -32,7 +32,7 @@ float WeaponAnimationTime::getValue() const
|
||||||
void WeaponAnimationTime::setGroup(const std::string &group)
|
void WeaponAnimationTime::setGroup(const std::string &group)
|
||||||
{
|
{
|
||||||
mWeaponGroup = group;
|
mWeaponGroup = group;
|
||||||
mStartTime = mAnimation->getStartTime(mWeaponGroup);
|
mStartTime = mAnimation->getStartTime(mWeaponGroup, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WeaponAnimationTime::updateStartTime()
|
void WeaponAnimationTime::updateStartTime()
|
||||||
|
|
Loading…
Reference in a new issue