mirror of
https://github.com/UberGames/EF2GameSource.git
synced 2024-11-10 06:31:42 +00:00
863 lines
23 KiB
C++
863 lines
23 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Logfile:: /EF2/Code/DLLs/game/actor_combatsubsystem.cpp $
|
|
// $Revision:: 65 $
|
|
// $Author:: Singlis $
|
|
// $Date:: 9/26/03 2:35p $
|
|
//
|
|
// Copyright (C) 2001 by Ritual Entertainment, Inc.
|
|
// All rights reserved.
|
|
//
|
|
// This source may not be distributed and/or modified without
|
|
// expressly written permission by Ritual Entertainment, Inc.
|
|
//
|
|
//
|
|
|
|
#include "_pch_cpp.h"
|
|
#include "object.h"
|
|
#include <qcommon/gameplaymanager.h>
|
|
|
|
|
|
//======================================
|
|
// CombatSubsystem Implementation
|
|
//======================================
|
|
|
|
//
|
|
// Name: CombatSubsystem()
|
|
// Parameters: None
|
|
// Description: Constructor()
|
|
//
|
|
CombatSubsystem::CombatSubsystem()
|
|
{
|
|
// Should always use other constructor
|
|
gi.Error(ERR_FATAL, "CombatSubsystem::CombatSubsystem -- Default Constructor Called");
|
|
}
|
|
|
|
|
|
//
|
|
// Name: CombatSubsystem()
|
|
// Parameters: Actor *actor
|
|
// Description: Constructor
|
|
//
|
|
CombatSubsystem::CombatSubsystem(Actor* actor)
|
|
{
|
|
//Initialize our Actor
|
|
if (actor)
|
|
act = actor;
|
|
else
|
|
gi.Error(ERR_DROP, "MovementSubsystem::MovementSubsystem -- actor is NULL");
|
|
|
|
_init();
|
|
}
|
|
|
|
|
|
//
|
|
// Name: ~CombatSubystem()
|
|
// Parameters: None
|
|
// Description: Destructor
|
|
//
|
|
CombatSubsystem::~CombatSubsystem()
|
|
{
|
|
}
|
|
|
|
|
|
//
|
|
// Name: UseActorWeapon()
|
|
// Parameters: const str &weaponName
|
|
// Description: Sets the weapon to be active, and attaches it
|
|
//
|
|
void CombatSubsystem::UseActorWeapon(const str& weaponName, weaponhand_t hand)
|
|
{
|
|
//First see if we just want to put our current item away
|
|
if (!stricmp(weaponName.c_str(), "none"))
|
|
{
|
|
act->DeactivateWeapon(WEAPON_LEFT);
|
|
act->DeactivateWeapon(WEAPON_RIGHT);
|
|
act->DeactivateWeapon(WEAPON_DUAL);
|
|
act->AddStateFlag(StateFlagChangedWeapon);
|
|
act->ClearTorsoAnim();
|
|
_activeWeapon.weapon = nullptr;
|
|
|
|
return;
|
|
}
|
|
|
|
auto weapon = dynamic_cast<Weapon *>(act->FindItem(weaponName.c_str()));
|
|
|
|
// Check to see if player has the weapon
|
|
if (!weapon)
|
|
{
|
|
act->warning("CombatSubsystem::UseActorWeapon", "Actor does not have weapon %s", weaponName.c_str());
|
|
return;
|
|
}
|
|
|
|
// If we don't already have an active weapon, just go ahead and make this one active
|
|
if (!_activeWeapon.weapon)
|
|
{
|
|
_activeWeapon.weapon = weapon;
|
|
_activeWeapon.hand = hand;
|
|
_activeWeapon.weapon->SetOwner(act);
|
|
act->ActivateWeapon(weapon, hand);
|
|
act->AddStateFlag(StateFlagChangedWeapon);
|
|
return;
|
|
}
|
|
|
|
// See if we are already using this weapon
|
|
if (weapon == _activeWeapon.weapon && hand == _activeWeapon.hand)
|
|
return;
|
|
|
|
// Well, we have a weapon in the same hand, put away the weapon thats there.
|
|
auto oldweapon = act->GetActiveWeapon(hand);
|
|
if (oldweapon)
|
|
act->DeactivateWeapon(hand);
|
|
|
|
// Activate this weapon
|
|
_activeWeapon.weapon = weapon;
|
|
_activeWeapon.hand = hand;
|
|
_activeWeapon.weapon->SetOwner(act);
|
|
act->ActivateWeapon(weapon, hand);
|
|
act->AddStateFlag(StateFlagChangedWeapon);
|
|
}
|
|
|
|
//
|
|
// Name: UsingWeaponNamed()
|
|
// Parameters: None
|
|
// Description: Checks if our _activeWeapon has the same name
|
|
//
|
|
bool CombatSubsystem::UsingWeaponNamed(const str& weaponName)
|
|
{
|
|
if (!_activeWeapon.weapon)
|
|
return false;
|
|
|
|
|
|
if (!Q_stricmp(_activeWeapon.weapon->getName().c_str(), weaponName.c_str()))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Name: HaveWeapon()
|
|
// Parameters: None
|
|
// Description: Returns True or False depending on _activeWeapon
|
|
//
|
|
bool CombatSubsystem::HaveWeapon()
|
|
{
|
|
if (_activeWeapon.weapon)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CombatSubsystem::CanAttackEnemy()
|
|
{
|
|
Entity* currentEnemy = act->enemyManager->GetCurrentEnemy();
|
|
if (!currentEnemy)
|
|
return false;
|
|
|
|
return CanAttackTarget(currentEnemy);
|
|
}
|
|
|
|
bool CombatSubsystem::WeaponIsFireType(firetype_t fire_type)
|
|
{
|
|
if (!_activeWeapon.weapon)
|
|
return false;
|
|
|
|
if (_activeWeapon.weapon->GetFireType(FIRE_MODE1) == fire_type)
|
|
return true;
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Name: CanShootTarget()
|
|
// Parameters: Entity *target
|
|
// Description: Checks if the Actor can shoot the target from its currentPosition
|
|
//
|
|
bool CombatSubsystem::CanAttackTarget(Entity* target)
|
|
{
|
|
if (!target)
|
|
return false;
|
|
|
|
if (act->CurrentAnim(legs) < 0)
|
|
return false;
|
|
|
|
Vector startPos;
|
|
if (_activeWeapon.weapon && FT_MELEE != _activeWeapon.weapon->GetFireType(FIRE_MODE1))
|
|
GetGunPositionData(&startPos);
|
|
else
|
|
startPos = act->centroid;
|
|
|
|
return CanAttackTargetFrom(target, startPos);
|
|
}
|
|
|
|
|
|
//
|
|
// Name: CanShootTargetFrom()
|
|
// Parameters: Entity *target
|
|
// Vector &startPos
|
|
// Description: Checks if the Actor can shoot the target from startPos
|
|
//
|
|
bool CombatSubsystem::CanAttackTargetFrom(Entity* target, const Vector& startPos)
|
|
{
|
|
if (_activeWeapon.weapon && !IsTargetInWeaponRange(target) || !target)
|
|
return false;
|
|
|
|
//Make sure target isn't too far "behind" us.
|
|
auto startToTarget = target->centroid - startPos;
|
|
startToTarget.normalize();
|
|
|
|
if (_activeWeapon.weapon && DotProduct(startToTarget, act->movementSubsystem->getAnimDir()) <= -.10)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
//We don't want to check constantly
|
|
if (level.time < _nextTimeTracedToTarget)
|
|
return _canShootTarget;
|
|
|
|
_canShootTarget = _traceHitTarget(target, startPos);
|
|
_nextTimeTracedToTarget = level.time + _traceInterval + G_Random();
|
|
//_nextTimeTracedToTarget = 0.0f;
|
|
|
|
return _canShootTarget;
|
|
}
|
|
|
|
//
|
|
// Name: IsTargetInWeaponRange()
|
|
// Parameters: Entity *target
|
|
// Description: Checks if the target is within the range of _activeWeapon
|
|
//
|
|
bool CombatSubsystem::IsTargetInWeaponRange(Entity* target)
|
|
{
|
|
if (!_activeWeapon.weapon)
|
|
return false;
|
|
|
|
auto distance = target->origin - act->origin;
|
|
|
|
if (distance.length() <= _activeWeapon.weapon->GetMaxRange())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//
|
|
// Name: SetTraceInterval()
|
|
// Parameters: float interval
|
|
// Description: Sets _traceInterval
|
|
//
|
|
void CombatSubsystem::SetTraceInterval(const float interval)
|
|
{
|
|
_traceInterval = interval;
|
|
}
|
|
|
|
|
|
//
|
|
// Name: DoArchive()
|
|
// Parameters: Archiver &arc
|
|
// Actor *actor
|
|
// Description: Sets the Actor Pointer and Calls Archive()
|
|
//
|
|
void CombatSubsystem::DoArchive(Archiver& arc, Actor* actor)
|
|
{
|
|
Archive(arc);
|
|
if (actor)
|
|
act = actor;
|
|
else
|
|
gi.Error(ERR_FATAL, "MovementSubsystem::DoArchive -- actor is NULL");
|
|
}
|
|
|
|
void CombatSubsystem::FireWeapon()
|
|
{
|
|
if (_activeWeapon.weapon)
|
|
_activeWeapon.weapon->Fire(WeaponModeNameToNum("primary"));
|
|
}
|
|
|
|
void CombatSubsystem::StopFireWeapon()
|
|
{
|
|
if (_activeWeapon.weapon)
|
|
{
|
|
if (_activeWeapon.weapon->animate->HasAnim("endfire"))
|
|
_activeWeapon.weapon->SetAnim("endfire");
|
|
else
|
|
_activeWeapon.weapon->ForceIdle();
|
|
}
|
|
}
|
|
|
|
void CombatSubsystem::AimWeaponTag(const Vector& targetPos)
|
|
{
|
|
Vector aimAngles;
|
|
Vector gunPos, gunForward, gunRight, gunUp;
|
|
|
|
//currentEnemy = act->enemyManager->GetCurrentEnemy();
|
|
|
|
if (!_activeWeapon.weapon)
|
|
return;
|
|
|
|
_activeWeapon.weapon->setOrigin();
|
|
_activeWeapon.weapon->setAngles();
|
|
|
|
_activeWeapon.weapon->SetControllerAngles(WEAPONBONE_BARREL_TAG, vec_zero);
|
|
|
|
GetGunPositionData(&gunPos, &gunForward, &gunRight, &gunUp);
|
|
|
|
Vector mypos, myforward, myleft, myup;
|
|
_activeWeapon.weapon->GetTag(WEAPONBONE_BARREL_TAG, &mypos, &myforward, &myleft, &myup);
|
|
|
|
float gfToWorld[ 3 ][ 3 ];
|
|
float worldToGf[ 3 ][ 3 ];
|
|
gunForward.copyTo(gfToWorld[0]);
|
|
gunRight.copyTo(gfToWorld[1]);
|
|
gunUp.copyTo(gfToWorld[2]);
|
|
|
|
TransposeMatrix(gfToWorld, worldToGf);
|
|
|
|
auto barrelToEnemyVector = targetPos - gunPos;
|
|
vec3_t barrelToEnemyVec3_t;
|
|
barrelToEnemyVector.copyTo(barrelToEnemyVec3_t);
|
|
|
|
vec3_t barrelToEnemyTransformedVec3_t;
|
|
MatrixTransformVector(barrelToEnemyVec3_t, worldToGf, barrelToEnemyTransformedVec3_t);
|
|
Vector barrelToEnemyTransformed(barrelToEnemyTransformedVec3_t);
|
|
barrelToEnemyTransformed.normalize();
|
|
barrelToEnemyTransformed = barrelToEnemyTransformed.toAngles();
|
|
barrelToEnemyTransformed.EulerNormalize();
|
|
aimAngles = -barrelToEnemyTransformed;
|
|
|
|
Vector aimUp, aimLeft, aimForward;
|
|
float spreadX, spreadY;
|
|
aimAngles.AngleVectors(&aimForward, &aimLeft, &aimUp);
|
|
spreadX = GetDataForMyWeapon("spreadx");
|
|
spreadY = GetDataForMyWeapon("spready");
|
|
|
|
// figure the new projected impact point based upon computed spread
|
|
if (_activeWeapon.weapon->GetFireType(FIRE_MODE1) == FT_PROJECTILE)
|
|
{
|
|
// Apply Spread
|
|
aimForward = aimForward * _activeWeapon.weapon->GetRange(FIRE_MODE1) +
|
|
aimLeft * G_CRandom(spreadX) +
|
|
aimUp * G_CRandom(spreadY);
|
|
}
|
|
|
|
// after figuring spread location, re-normalize vectors
|
|
aimForward.normalize();
|
|
|
|
aimAngles = aimForward.toAngles();
|
|
|
|
_activeWeapon.weapon->SetControllerTag(WEAPONBONE_BARREL_TAG, gi.Tag_NumForName(_activeWeapon.weapon->edict->s.modelindex, "tag_barrel"));
|
|
_activeWeapon.weapon->SetControllerAngles(WEAPONBONE_BARREL_TAG, aimAngles);
|
|
|
|
//Draw Trace
|
|
if (g_showbullettrace->integer)
|
|
{
|
|
Vector test;
|
|
GetGunPositionData(&gunPos, &gunForward);
|
|
test = gunForward * 1000 + gunPos;
|
|
|
|
G_DebugLine(gunPos, test, 1.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
Vector barrelForward;
|
|
aimAngles.AngleVectors(&barrelForward);
|
|
}
|
|
}
|
|
|
|
void CombatSubsystem::AimWeaponTag(Entity* target)
|
|
{
|
|
auto targetBone = target->getTargetPos();
|
|
auto targetPos = target->centroid;
|
|
|
|
if (targetBone.length())
|
|
{
|
|
if (gi.Tag_NumForName(target->edict->s.modelindex, targetBone) > 0)
|
|
{
|
|
target->GetTag(targetBone.c_str(), &targetPos, nullptr, nullptr, nullptr);
|
|
}
|
|
}
|
|
|
|
auto newTargetPos = targetPos;
|
|
|
|
if (!_activeWeapon.weapon)
|
|
return;
|
|
|
|
if (_activeWeapon.weapon->GetFireType(FIRE_MODE1) == FT_PROJECTILE)
|
|
newTargetPos = GetLeadingTargetPos(_activeWeapon.weapon->GetProjectileSpeed(), targetPos, target);
|
|
|
|
AimWeaponTag(newTargetPos);
|
|
}
|
|
|
|
void CombatSubsystem::ClearAim()
|
|
{
|
|
_activeWeapon.weapon->SetControllerTag(WEAPONBONE_BARREL_TAG, gi.Tag_NumForName(_activeWeapon.weapon->edict->s.modelindex, "tag_barrel"));
|
|
_activeWeapon.weapon->SetControllerAngles(WEAPONBONE_BARREL_TAG, vec_zero);
|
|
}
|
|
|
|
//
|
|
// Name: Archive()
|
|
// Parameters: Archiver &arc
|
|
// Description: Archives Class Data
|
|
//
|
|
void CombatSubsystem::Archive(Archiver& arc)
|
|
{
|
|
_activeWeapon.Archive(arc);
|
|
arc.ArchiveFloat(&_nextTimeTracedToTarget);
|
|
arc.ArchiveFloat(&_traceInterval);
|
|
arc.ArchiveBool(&_canShootTarget);
|
|
|
|
arc.ArchiveFloat(&_yawDiff);
|
|
}
|
|
|
|
|
|
//
|
|
// Name: GetGunPosition()
|
|
// Parameters: None
|
|
// Description: Gets the GunBarrel Position
|
|
//
|
|
void CombatSubsystem::GetGunPositionData(Vector* pos, Vector* forward, Vector* right, Vector* up)
|
|
{
|
|
int tag_num;
|
|
|
|
if (!_activeWeapon.weapon)
|
|
gi.Error(ERR_FATAL, "Actor has no activeweapon");
|
|
|
|
tag_num = gi.Tag_NumForName(_activeWeapon.weapon->edict->s.modelindex, "tag_barrel");
|
|
if (tag_num < 0)
|
|
gi.Error(ERR_FATAL, "Weapon has no tag_barrel");
|
|
|
|
_activeWeapon.weapon->GetActorMuzzlePosition(pos, forward, right, up);
|
|
}
|
|
|
|
float CombatSubsystem::GetAimGunYaw(const Vector& target)
|
|
{
|
|
Vector GunPos, GunForward, GunRight, GunUp;
|
|
|
|
// Get our Gun Data
|
|
GetGunPositionData(&GunPos, &GunForward, &GunRight, &GunUp);
|
|
|
|
// Get Vectors to Gun and To Target;
|
|
auto GunPosToOrigin = act->origin - GunPos;
|
|
auto OriginToGunPos = GunPos - act->origin;
|
|
auto OriginToTarget = target - act->origin;
|
|
|
|
// Zero out the Z
|
|
GunPosToOrigin.z = 0.0f;
|
|
OriginToTarget.z = 0.0f;
|
|
OriginToGunPos.z = 0.0f;
|
|
|
|
// Get Lengths
|
|
auto a = GunPosToOrigin.length();
|
|
auto d = OriginToTarget.length();
|
|
|
|
// Get Angle Difference
|
|
auto TagForwardAngles = GunForward.toAngles();
|
|
auto GunToOriginAngles = GunPosToOrigin.toAngles();
|
|
auto OriginToGunAngles = OriginToGunPos.toAngles();
|
|
auto OriginToTargetAngles = OriginToTarget.toAngles();
|
|
|
|
auto AngleDiff = AngleNormalize180(GunToOriginAngles[YAW] - TagForwardAngles[YAW]);
|
|
AngleDiff = 180.0f - AngleDiff;
|
|
|
|
auto r = a * sin(DEG2RAD(AngleDiff));
|
|
auto angleA = RAD2DEG(acos ( r / d ));
|
|
auto angleB = RAD2DEG(acos ( r / a ));
|
|
auto angleC = AngleNormalize180(OriginToTargetAngles[YAW] - OriginToGunAngles[YAW]);
|
|
auto totalAngle = angleB + angleC;
|
|
auto finalAngle = totalAngle - angleA;
|
|
|
|
return AngleNormalize180(finalAngle);
|
|
}
|
|
|
|
|
|
float CombatSubsystem::GetAimGunPitch(const Vector& target)
|
|
{
|
|
Vector GunPos, GunForward, GunRight, GunUp;
|
|
|
|
// Get our Gun Data
|
|
GetGunPositionData(&GunPos, &GunForward, &GunRight, &GunUp);
|
|
|
|
auto TagToTarget = target - GunPos;
|
|
return AngleNormalize180(TagToTarget.toAngles()[PITCH] - GunForward.toAngles()[PITCH]);
|
|
}
|
|
|
|
//
|
|
// Name: _traceHitTarget()
|
|
// Parameters: Entity *target
|
|
// const Vector &startPos
|
|
// Description: Checks of the a trace hits the target
|
|
//
|
|
bool CombatSubsystem::_traceHitTarget(Entity* target, const Vector& startPos)
|
|
{
|
|
auto targetBone = target->getTargetPos();
|
|
auto targetPos = target->centroid;
|
|
|
|
if (targetBone.length())
|
|
{
|
|
if (gi.Tag_NumForName(target->edict->s.modelindex, targetBone) > 0)
|
|
{
|
|
target->GetTag(targetBone.c_str(), &targetPos, nullptr, nullptr, nullptr);
|
|
}
|
|
}
|
|
|
|
auto attackDir = targetPos - startPos;
|
|
auto attackDirLength = attackDir.length();
|
|
attackDir.normalize();
|
|
attackDir *= attackDirLength + 32.0f;
|
|
attackDir += startPos;
|
|
targetPos = attackDir;
|
|
|
|
auto trace = G_FullTrace(startPos, vec_zero, vec_zero, targetPos, act, MASK_SHOT, false, "CombatSubsystem::_traceHitTarget");
|
|
//G_DebugLine( startPos, targetPos, .5f, 1.0f, .5f, 1.0f );
|
|
|
|
if (trace.startsolid)
|
|
{
|
|
if (trace.ent == target->edict)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// see if we hit anything at all
|
|
if (!trace.ent)
|
|
return false;
|
|
|
|
// If we hit the guy we wanted, then shoot
|
|
if (trace.ent == target->edict)
|
|
return true;
|
|
|
|
// If we hit someone else we don't like, then shoot
|
|
auto t = trace.ent->entity;
|
|
if (act->enemyManager->IsValidEnemy(t))
|
|
{
|
|
act->enemyManager->SetCurrentEnemy(trace.ent->entity);
|
|
// We want to return false though, so we don't add
|
|
// the attempted enemy to the hatelist
|
|
return false;
|
|
}
|
|
|
|
|
|
// if we hit something breakable, check if shooting it will
|
|
// let us shoot someone.
|
|
if (t->isSubclassOf( Object ) || t->isSubclassOf( ScriptModel ))
|
|
{
|
|
trace = G_Trace(Vector(trace.endpos), vec_zero, vec_zero, target->centroid, t, MASK_SHOT, false, "CombatSubsystem::_traceHitTarget 2");
|
|
if (trace.startsolid)
|
|
return false;
|
|
|
|
// see if we hit anything at all
|
|
if (!trace.ent)
|
|
return false;
|
|
|
|
// If we hit the guy we wanted, then shoot
|
|
if (trace.ent == target->edict)
|
|
return true;
|
|
|
|
// If we hit someone else we don't like, then shoot
|
|
if (act->enemyManager->IsValidEnemy(trace.ent->entity))
|
|
return true;
|
|
|
|
// Forget it then
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Name: _init()
|
|
// Parameters: None
|
|
// Description: Initializes Class Data
|
|
//
|
|
void CombatSubsystem::_init()
|
|
{
|
|
_activeWeapon.weapon = nullptr;
|
|
_nextTimeTracedToTarget = 0;
|
|
_traceInterval = DEFAULT_TRACE_INTERVAL;
|
|
_canShootTarget = false;
|
|
_yawDiff = 0.0f;
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
// Name: GetBestAvailableWeapon()
|
|
// Class: CombatSubsystem
|
|
//
|
|
// Description: Iterates through the actor's inventory list and
|
|
// checks the power rating ( modified by range and armor )
|
|
// of each weapon, returning the best one.
|
|
//
|
|
// Parameters: Entity *target -- The target we are trying to shoot
|
|
//
|
|
// Returns: None
|
|
//--------------------------------------------------------------
|
|
WeaponPtr CombatSubsystem::GetBestAvailableWeapon(Entity* target)
|
|
{
|
|
Weapon* bestWeapon = nullptr;
|
|
auto bestPowerRating = 0.0f;
|
|
|
|
// Try and grab the first item. If you pass a null into the
|
|
// NextItem() it will attempt to grab the first item in the
|
|
// inventory list
|
|
auto item = act->NextItem(nullptr);
|
|
|
|
// Loop as long as we have inventory items to check
|
|
while (item)
|
|
{
|
|
if (item->isSubclassOf(Weapon))
|
|
{
|
|
auto weapon = dynamic_cast<Weapon*>(item);
|
|
if (weapon)
|
|
{
|
|
auto powerRating = getModifiedPowerRating(target, weapon);
|
|
if (powerRating > bestPowerRating)
|
|
{
|
|
bestPowerRating = powerRating;
|
|
bestWeapon = weapon;
|
|
}
|
|
}
|
|
}
|
|
|
|
item = act->NextItem(item);
|
|
}
|
|
|
|
return bestWeapon;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------
|
|
// Name: getModifiedPowerRating()
|
|
// Class: CombatSubsystem
|
|
//
|
|
// Description: Returns the powerRating of the weapon modified by
|
|
// such conditions as Armor, Range, Spread, and speed
|
|
// of the projectile
|
|
//
|
|
// Parameters: Entity *target -- Target we're trying to shoot
|
|
// Weapon *weapon -- Weapon we're trying to shoot with
|
|
//
|
|
// Returns: weaponPowerRating
|
|
//--------------------------------------------------------------
|
|
float CombatSubsystem::getModifiedPowerRating(Entity* target, Weapon* weapon)
|
|
{
|
|
float rangeToTarget;
|
|
float weaponPowerRating;
|
|
float weaponRange;
|
|
float weaponSpread;
|
|
float hitPercentage;
|
|
float skillLevel;
|
|
Actor* actTarget;
|
|
Vector selfToTarget;
|
|
|
|
// If we don't have a target we can't shoot it
|
|
if (!target)
|
|
{
|
|
//Assert to let us know this occurred
|
|
assert ( target != NULL );
|
|
return 0.0f;
|
|
}
|
|
|
|
// Set our default values
|
|
weaponPowerRating = weapon->GetPowerRating();
|
|
weaponRange = weapon->GetRange(FIRE_MODE1);
|
|
|
|
// Calculate our distance to the target
|
|
selfToTarget = target->origin - act->origin;
|
|
rangeToTarget = selfToTarget.length();
|
|
|
|
|
|
// If we are out of range, we can't shoot target
|
|
if (rangeToTarget > weaponRange)
|
|
return 0.0f;
|
|
|
|
// Check if we can even hurt the target with this weapon
|
|
if (target->isSubclassOf(Actor))
|
|
{
|
|
actTarget = dynamic_cast<Actor*>(target);
|
|
if (!actTarget->canBeDamagedBy(weapon->GetMeansOfDeath(FIRE_MODE1)))
|
|
return 0.0f;
|
|
}
|
|
|
|
//Calculate our hit %
|
|
if ((weapon->flags |= FT_BULLET) || (weapon->flags |= FT_MELEE))
|
|
{
|
|
weaponSpread = weapon->GetBulletSpreadX(FIRE_MODE1);
|
|
if (weaponSpread < 1.0)
|
|
weaponSpread = 1.0f;
|
|
|
|
hitPercentage = rangeToTarget / weaponRange * weaponSpread;
|
|
}
|
|
else
|
|
{
|
|
hitPercentage = weapon->GetProjectileSpeed() / rangeToTarget;
|
|
}
|
|
|
|
// Get our skill level with this weapon -- Should be a number between 0.0 and 1.0
|
|
// all skill levels default to 1.0 so it won't affect anything directly
|
|
skillLevel = weapon->GetSkillLevel();
|
|
|
|
return hitPercentage * weaponPowerRating * skillLevel;
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
// Name: GetActiveWeaponPowerRating()
|
|
// Class: CombatSubsystem
|
|
//
|
|
// Description: Returns the power rating of the ActiveWeapon
|
|
//
|
|
// Parameters: Entity *target
|
|
//
|
|
// Returns: float
|
|
//--------------------------------------------------------------
|
|
float CombatSubsystem::GetActiveWeaponPowerRating(Entity* target)
|
|
{
|
|
if (_activeWeapon.weapon)
|
|
return getModifiedPowerRating(target, _activeWeapon.weapon);
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
str CombatSubsystem::GetActiveWeaponName()
|
|
{
|
|
str name;
|
|
|
|
if (_activeWeapon.weapon)
|
|
name = _activeWeapon.weapon->getName();
|
|
|
|
return name;
|
|
}
|
|
|
|
str CombatSubsystem::GetActiveWeaponArchetype()
|
|
{
|
|
str atype;
|
|
|
|
if (_activeWeapon.weapon)
|
|
atype = _activeWeapon.weapon->getArchetype();
|
|
|
|
return atype;
|
|
}
|
|
|
|
bool CombatSubsystem::GetProjectileLaunchAngles(Vector& launchAngles, const Vector& launchPoint, const float initialSpeed, const float gravity, const bool useHighTrajectory) const
|
|
{
|
|
Entity const* target = act->enemyManager->GetCurrentEnemy();
|
|
|
|
if (target)
|
|
{
|
|
auto targetPoint(target->centroid);
|
|
|
|
Trajectory projectileTrajectory(launchPoint, targetPoint, initialSpeed, gravity * -sv_currentGravity->value, useHighTrajectory);
|
|
if (projectileTrajectory.GetTravelTime() > 0.0f)
|
|
{
|
|
launchAngles.setPitch(projectileTrajectory.GetLaunchAngle());
|
|
|
|
auto direction(targetPoint - launchPoint);
|
|
direction.z = 0.0f;
|
|
direction.normalize();
|
|
|
|
launchAngles.setYaw(direction.toYaw());
|
|
launchAngles.setRoll(0.0f);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CombatSubsystem::shouldArcProjectile()
|
|
{
|
|
if (_activeWeapon.weapon)
|
|
return _activeWeapon.weapon->shouldArcProjectile();
|
|
|
|
return false;
|
|
}
|
|
|
|
float CombatSubsystem::GetLowArcRange()
|
|
{
|
|
if (_activeWeapon.weapon)
|
|
return _activeWeapon.weapon->GetLowArcRange();
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
str CombatSubsystem::GetAnimForMyWeapon(const str& property)
|
|
{
|
|
auto gpm = GameplayManager::getTheGameplayManager();
|
|
if (!gpm->hasObject(act->getArchetype()))
|
|
return "";
|
|
|
|
auto objname = act->combatSubsystem->GetActiveWeaponArchetype();
|
|
objname = "Hold" + objname;
|
|
|
|
if (gpm->hasProperty(objname, property))
|
|
return gpm->getStringValue(objname, property);
|
|
|
|
return "";
|
|
}
|
|
|
|
float CombatSubsystem::GetDataForMyWeapon(const str& property)
|
|
{
|
|
auto gpm = GameplayManager::getTheGameplayManager();
|
|
if (!gpm->hasObject(act->getArchetype()))
|
|
return 0.0;
|
|
|
|
auto objname = act->combatSubsystem->GetActiveWeaponArchetype();
|
|
objname = "Hold" + objname;
|
|
|
|
if (gpm->hasProperty(objname, property))
|
|
return gpm->getFloatValue(objname, property);
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
void CombatSubsystem::OverrideSpread(float spreadX, float spreadY)
|
|
{
|
|
if (!_activeWeapon.weapon)
|
|
return;
|
|
|
|
_activeWeapon.weapon->SetBulletSpread(spreadX, spreadY);
|
|
}
|
|
|
|
Vector CombatSubsystem::GetLeadingTargetPos(float projSpeed, Vector originalTargetPos, Entity* target)
|
|
{
|
|
auto newTargetPos = originalTargetPos;
|
|
|
|
if (!target)
|
|
return newTargetPos;
|
|
|
|
if (target->groundentity)
|
|
{
|
|
// Here we're going to try and lead the target;
|
|
auto targetVelocity = target->velocity;
|
|
if (projSpeed <= 0)
|
|
projSpeed = 1;
|
|
|
|
auto selfToTarget = originalTargetPos - act->centroid;
|
|
auto distToTarget = selfToTarget.length();
|
|
auto timeToTarget = distToTarget / projSpeed;
|
|
auto enemyMoveDir = targetVelocity;
|
|
auto targetSpeed = targetVelocity.length();
|
|
|
|
enemyMoveDir.normalize();
|
|
|
|
// Parameterize aim leading over the interval [minLeadFactor,maxLeadFactor]
|
|
// such that 0 is targetPos and 1 is newTargetPos.
|
|
//
|
|
// Select a random parameter value in that range and interpolate
|
|
// between the two positions.
|
|
// Essentially, the shot will aim at some random point between
|
|
// where the target is now and where it (presumably) will be
|
|
// given the lead distance.
|
|
//
|
|
// When <leadFraction> is 0, shots aim at the current position
|
|
// of the target. When <leadFracation> is 1, shots aim at
|
|
// the best-guess lead-position of the target. When <leadFraction>
|
|
// is greater than 1, shots will over-lead the player. Note
|
|
// that in the latter case shots may actually hit the player
|
|
// if he is circle-strafing or moving closer/farther in addition
|
|
// to his lateral movement.
|
|
//
|
|
auto leadFraction = act->minLeadFactor + G_Random(act->maxLeadFactor - act->minLeadFactor);
|
|
auto leadVector = enemyMoveDir * targetSpeed * timeToTarget;
|
|
newTargetPos = originalTargetPos + leadFraction * leadVector;
|
|
}
|
|
|
|
|
|
return newTargetPos;
|
|
}
|