2022-09-18 15:37:21 +00:00
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Copyright ( C ) 2000 - 2013 , Raven Software , Inc .
Copyright ( C ) 2001 - 2013 , Activision , Inc .
Copyright ( C ) 2013 - 2015 , OpenJK contributors
This file is part of the OpenJK source code .
OpenJK is free software ; you can redistribute it and / or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , see < http : //www.gnu.org/licenses/>.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
//NPC_utils.cpp
# include "b_local.h"
# include "Q3_Interface.h"
# include "g_navigator.h"
# include "../cgame/cg_local.h"
# include "g_nav.h"
extern Vehicle_t * G_IsRidingVehicle ( gentity_t * pEnt ) ;
int teamNumbers [ TEAM_NUM_TEAMS ] ;
int teamStrength [ TEAM_NUM_TEAMS ] ;
int teamCounter [ TEAM_NUM_TEAMS ] ;
# define VALID_ATTACK_CONE 2.0f //Degrees
void GetAnglesForDirection ( const vec3_t p1 , const vec3_t p2 , vec3_t out ) ;
/*
void CalcEntitySpot ( gentity_t * ent , spot_t spot , vec3_t point )
Added : Uses shootAngles if a NPC has them
*/
extern void ViewHeightFix ( const gentity_t * const ent ) ;
extern void AddLeanOfs ( const gentity_t * const ent , vec3_t point ) ;
extern void SubtractLeanOfs ( const gentity_t * const ent , vec3_t point ) ;
void CalcEntitySpot ( const gentity_t * ent , const spot_t spot , vec3_t point )
{
vec3_t forward , up , right ;
vec3_t start , end ;
trace_t tr ;
if ( ! ent )
{
return ;
}
ViewHeightFix ( ent ) ;
switch ( spot )
{
case SPOT_ORIGIN :
if ( VectorCompare ( ent - > currentOrigin , vec3_origin ) )
{ //brush
VectorSubtract ( ent - > absmax , ent - > absmin , point ) ; //size
VectorMA ( ent - > absmin , 0.5 , point , point ) ;
}
else
{
VectorCopy ( ent - > currentOrigin , point ) ;
}
break ;
case SPOT_CHEST :
case SPOT_HEAD :
if ( ent - > client & & VectorLengthSquared ( ent - > client - > renderInfo . eyePoint ) & & ( ent - > client - > ps . viewEntity < = 0 | | ent - > client - > ps . viewEntity > = ENTITYNUM_WORLD ) )
{ //Actual tag_head eyespot!
//FIXME: Stasis aliens may have a problem here...
VectorCopy ( ent - > client - > renderInfo . eyePoint , point ) ;
if ( ent - > client - > NPC_class = = CLASS_ATST )
{ //adjust up some
point [ 2 ] + = 28 ; //magic number :)
2023-03-05 19:52:49 +00:00
} else if ( ! ent - > client - > ps . clientNum ) {
// When IRL crouch is used, view height is higher than model height
// We need to lower "point" else enemies will be aiming into empty space
int viewHeight = ent - > client - > ps . viewheight - STANDARD_VIEWHEIGHT_OFFSET ;
int realHeight = ent - > maxs [ 2 ] ;
point [ 2 ] - = viewHeight - realHeight ;
2022-09-18 15:37:21 +00:00
}
if ( ent - > NPC )
{ //always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards
point [ 0 ] = ent - > currentOrigin [ 0 ] ;
point [ 1 ] = ent - > currentOrigin [ 1 ] ;
}
else if ( ! ent - > s . number )
{
SubtractLeanOfs ( ent , point ) ;
}
}
else
{
VectorCopy ( ent - > currentOrigin , point ) ;
if ( ent - > client )
{
point [ 2 ] + = ent - > client - > ps . viewheight ;
}
}
if ( spot = = SPOT_CHEST & & ent - > client )
{
if ( ent - > client - > NPC_class ! = CLASS_ATST )
{ //adjust up some
point [ 2 ] - = ent - > maxs [ 2 ] * 0.2f ;
}
}
break ;
case SPOT_HEAD_LEAN :
if ( ent - > client & & VectorLengthSquared ( ent - > client - > renderInfo . eyePoint ) & & ( ent - > client - > ps . viewEntity < = 0 | | ent - > client - > ps . viewEntity > = ENTITYNUM_WORLD ) )
{ //Actual tag_head eyespot!
//FIXME: Stasis aliens may have a problem here...
VectorCopy ( ent - > client - > renderInfo . eyePoint , point ) ;
if ( ent - > client - > NPC_class = = CLASS_ATST )
{ //adjust up some
point [ 2 ] + = 28 ; //magic number :)
2023-03-05 19:52:49 +00:00
} else if ( ! ent - > client - > ps . clientNum ) {
// When IRL crouch is used, view height is higher than model height
// We need to lower "point" else enemies will be aiming into empty space
int viewHeight = ent - > client - > ps . viewheight - STANDARD_VIEWHEIGHT_OFFSET ;
int realHeight = ent - > maxs [ 2 ] ;
point [ 2 ] - = viewHeight - realHeight ;
2022-09-18 15:37:21 +00:00
}
if ( ent - > NPC )
{ //always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards
point [ 0 ] = ent - > currentOrigin [ 0 ] ;
point [ 1 ] = ent - > currentOrigin [ 1 ] ;
}
else if ( ! ent - > s . number )
{
SubtractLeanOfs ( ent , point ) ;
}
//NOTE: automatically takes leaning into account!
}
else
{
VectorCopy ( ent - > currentOrigin , point ) ;
if ( ent - > client )
{
point [ 2 ] + = ent - > client - > ps . viewheight ;
}
//AddLeanOfs ( ent, point );
}
break ;
//FIXME: implement...
//case SPOT_CHEST:
//Returns point 3/4 from tag_torso to tag_head?
//break;
case SPOT_LEGS :
VectorCopy ( ent - > currentOrigin , point ) ;
point [ 2 ] + = ( ent - > mins [ 2 ] * 0.5 ) ;
break ;
case SPOT_WEAPON :
if ( ent - > NPC & & ! VectorCompare ( ent - > NPC - > shootAngles , vec3_origin ) & & ! VectorCompare ( ent - > NPC - > shootAngles , ent - > client - > ps . viewangles ) )
{
AngleVectors ( ent - > NPC - > shootAngles , forward , right , up ) ;
}
else
{
AngleVectors ( ent - > client - > ps . viewangles , forward , right , up ) ;
}
CalcMuzzlePoint ( ( gentity_t * ) ent , forward , right , up , point , 0 ) ;
//NOTE: automatically takes leaning into account!
break ;
case SPOT_GROUND :
// if entity is on the ground, just use it's absmin
if ( ent - > s . groundEntityNum ! = - 1 )
{
VectorCopy ( ent - > currentOrigin , point ) ;
point [ 2 ] = ent - > absmin [ 2 ] ;
break ;
}
// if it is reasonably close to the ground, give the point underneath of it
VectorCopy ( ent - > currentOrigin , start ) ;
start [ 2 ] = ent - > absmin [ 2 ] ;
VectorCopy ( start , end ) ;
end [ 2 ] - = 64 ;
gi . trace ( & tr , start , ent - > mins , ent - > maxs , end , ent - > s . number , MASK_PLAYERSOLID , ( EG2_Collision ) 0 , 0 ) ;
if ( tr . fraction < 1.0 )
{
VectorCopy ( tr . endpos , point ) ;
break ;
}
// otherwise just use the origin
VectorCopy ( ent - > currentOrigin , point ) ;
break ;
default :
VectorCopy ( ent - > currentOrigin , point ) ;
break ;
}
}
//===================================================================================
/*
qboolean NPC_UpdateAngles ( qboolean doPitch , qboolean doYaw )
Added : option to do just pitch or just yaw
Does not include " aim " in it ' s calculations
FIXME : stop compressing angles into shorts ! ! ! !
*/
extern cvar_t * g_timescale ;
extern bool NPC_IsTrooper ( gentity_t * ent ) ;
qboolean NPC_UpdateAngles ( qboolean doPitch , qboolean doYaw )
{
# if 1
float error ;
float decay ;
float targetPitch = 0 ;
float targetYaw = 0 ;
float yawSpeed ;
qboolean exact = qtrue ;
// if angle changes are locked; just keep the current angles
// aimTime isn't even set anymore... so this code was never reached, but I need a way to lock NPC's yaw, so instead of making a new SCF_ flag, just use the existing render flag... - dmv
if ( ! NPC - > enemy & & ( ( level . time < NPCInfo - > aimTime ) | | NPC - > client - > renderInfo . renderFlags & RF_LOCKEDANGLE ) )
{
if ( doPitch )
targetPitch = NPCInfo - > lockedDesiredPitch ;
if ( doYaw )
targetYaw = NPCInfo - > lockedDesiredYaw ;
}
else
{
// we're changing the lockedDesired Pitch/Yaw below so it's lost it's original meaning, get rid of the lock flag
NPC - > client - > renderInfo . renderFlags & = ~ RF_LOCKEDANGLE ;
if ( doPitch )
{
targetPitch = NPCInfo - > desiredPitch ;
NPCInfo - > lockedDesiredPitch = NPCInfo - > desiredPitch ;
}
if ( doYaw )
{
targetYaw = NPCInfo - > desiredYaw ;
NPCInfo - > lockedDesiredYaw = NPCInfo - > desiredYaw ;
}
}
if ( NPC - > s . weapon = = WP_EMPLACED_GUN )
{
// FIXME: this seems to do nothing, actually...
yawSpeed = 20 ;
}
else
{
if ( NPC - > client - > NPC_class = = CLASS_ROCKETTROOPER
& & ! NPC - > enemy )
{ //just slowly lookin' around
yawSpeed = 1 ;
}
else
{
yawSpeed = NPCInfo - > stats . yawSpeed ;
}
}
if ( NPC - > s . weapon = = WP_SABER & & NPC - > client - > ps . forcePowersActive & ( 1 < < FP_SPEED ) )
{
yawSpeed * = 1.0f / g_timescale - > value ;
}
if ( ! NPC_IsTrooper ( NPC )
& & NPC - > enemy
& & ! G_IsRidingVehicle ( NPC )
& & NPC - > client - > NPC_class ! = CLASS_VEHICLE )
{
if ( NPC - > s . weapon = = WP_BLASTER_PISTOL | |
NPC - > s . weapon = = WP_BLASTER | |
NPC - > s . weapon = = WP_BOWCASTER | |
NPC - > s . weapon = = WP_REPEATER | |
NPC - > s . weapon = = WP_FLECHETTE | |
NPC - > s . weapon = = WP_BRYAR_PISTOL | |
NPC - > s . weapon = = WP_NOGHRI_STICK )
{
yawSpeed * = 10.0f ;
}
}
if ( doYaw )
{
// decay yaw error
error = AngleDelta ( NPC - > client - > ps . viewangles [ YAW ] , targetYaw ) ;
if ( fabs ( error ) > MIN_ANGLE_ERROR )
{
if ( error )
{
exact = qfalse ;
decay = 60.0 + yawSpeed * 3 ;
decay * = 50.0f / 1000.0f ; //msec
if ( error < 0.0 )
{
error + = decay ;
if ( error > 0.0 )
{
error = 0.0 ;
}
}
else
{
error - = decay ;
if ( error < 0.0 )
{
error = 0.0 ;
}
}
}
}
ucmd . angles [ YAW ] = ANGLE2SHORT ( targetYaw + error ) - client - > ps . delta_angles [ YAW ] ;
}
//FIXME: have a pitchSpeed?
if ( doPitch )
{
// decay pitch error
error = AngleDelta ( NPC - > client - > ps . viewangles [ PITCH ] , targetPitch ) ;
if ( fabs ( error ) > MIN_ANGLE_ERROR )
{
if ( error )
{
exact = qfalse ;
decay = 60.0 + yawSpeed * 3 ;
decay * = 50.0f / 1000.0f ; //msec
if ( error < 0.0 )
{
error + = decay ;
if ( error > 0.0 )
{
error = 0.0 ;
}
}
else
{
error - = decay ;
if ( error < 0.0 )
{
error = 0.0 ;
}
}
}
}
ucmd . angles [ PITCH ] = ANGLE2SHORT ( targetPitch + error ) - client - > ps . delta_angles [ PITCH ] ;
}
ucmd . angles [ ROLL ] = ANGLE2SHORT ( NPC - > client - > ps . viewangles [ ROLL ] ) - client - > ps . delta_angles [ ROLL ] ;
if ( exact & & Q3_TaskIDPending ( NPC , TID_ANGLE_FACE ) )
{
Q3_TaskIDComplete ( NPC , TID_ANGLE_FACE ) ;
}
return exact ;
# else
float error ;
float decay ;
float targetPitch = 0 ;
float targetYaw = 0 ;
float yawSpeed ;
//float runningMod = NPCInfo->currentSpeed/100.0f;
qboolean exact = qtrue ;
qboolean doSound = qfalse ;
// if angle changes are locked; just keep the current angles
if ( level . time < NPCInfo - > aimTime )
{
if ( doPitch )
targetPitch = NPCInfo - > lockedDesiredPitch ;
if ( doYaw )
targetYaw = NPCInfo - > lockedDesiredYaw ;
}
else
{
if ( doPitch )
targetPitch = NPCInfo - > desiredPitch ;
if ( doYaw )
targetYaw = NPCInfo - > desiredYaw ;
// NPCInfo->aimTime = level.time + 250;
if ( doPitch )
NPCInfo - > lockedDesiredPitch = NPCInfo - > desiredPitch ;
if ( doYaw )
NPCInfo - > lockedDesiredYaw = NPCInfo - > desiredYaw ;
}
yawSpeed = NPCInfo - > stats . yawSpeed ;
if ( doYaw )
{
// decay yaw error
error = AngleDelta ( NPC - > client - > ps . viewangles [ YAW ] , targetYaw ) ;
if ( fabs ( error ) > MIN_ANGLE_ERROR )
{
/*
if ( NPC - > client - > playerTeam = = TEAM_BORG & &
NPCInfo - > behaviorState ! = BS_FACE & & NPCInfo - > tempBehavior ! = BS_FACE )
{ //HACK - borg turn more jittery
if ( error )
{
exact = qfalse ;
decay = 60.0 + yawSpeed * 3 ;
decay * = 50.0 / 1000.0 ; //msec
//Snap to
if ( fabs ( error ) > 10 )
{
if ( Q_flrand ( 0.0f , 1.0f ) > 0.6 )
{
doSound = qtrue ;
}
}
if ( error < 0.0 ) //-10.0 )
{
error + = decay ;
if ( error > 0.0 )
{
error = 0.0 ;
}
}
else if ( error > 0.0 ) //10.0 )
{
error - = decay ;
if ( error < 0.0 )
{
error = 0.0 ;
}
}
}
}
else */
if ( error )
{
exact = qfalse ;
decay = 60.0 + yawSpeed * 3 ;
decay * = 50.0 / 1000.0 ; //msec
if ( error < 0.0 )
{
error + = decay ;
if ( error > 0.0 )
{
error = 0.0 ;
}
}
else
{
error - = decay ;
if ( error < 0.0 )
{
error = 0.0 ;
}
}
}
}
ucmd . angles [ YAW ] = ANGLE2SHORT ( targetYaw + error ) - client - > ps . delta_angles [ YAW ] ;
}
//FIXME: have a pitchSpeed?
if ( doPitch )
{
// decay pitch error
error = AngleDelta ( NPC - > client - > ps . viewangles [ PITCH ] , targetPitch ) ;
if ( fabs ( error ) > MIN_ANGLE_ERROR )
{
/*
if ( NPC - > client - > playerTeam = = TEAM_BORG & &
NPCInfo - > behaviorState ! = BS_FACE & & NPCInfo - > tempBehavior ! = BS_FACE )
{ //HACK - borg turn more jittery
if ( error )
{
exact = qfalse ;
decay = 60.0 + yawSpeed * 3 ;
decay * = 50.0 / 1000.0 ; //msec
//Snap to
if ( fabs ( error ) > 10 )
{
if ( Q_flrand ( 0.0f , 1.0f ) > 0.6 )
{
doSound = qtrue ;
}
}
if ( error < 0.0 ) //-10.0 )
{
error + = decay ;
if ( error > 0.0 )
{
error = 0.0 ;
}
}
else if ( error > 0.0 ) //10.0 )
{
error - = decay ;
if ( error < 0.0 )
{
error = 0.0 ;
}
}
}
}
else */
if ( error )
{
exact = qfalse ;
decay = 60.0 + yawSpeed * 3 ;
decay * = 50.0 / 1000.0 ; //msec
if ( error < 0.0 )
{
error + = decay ;
if ( error > 0.0 )
{
error = 0.0 ;
}
}
else
{
error - = decay ;
if ( error < 0.0 )
{
error = 0.0 ;
}
}
}
}
ucmd . angles [ PITCH ] = ANGLE2SHORT ( targetPitch + error ) - client - > ps . delta_angles [ PITCH ] ;
}
ucmd . angles [ ROLL ] = ANGLE2SHORT ( NPC - > client - > ps . viewangles [ ROLL ] ) - client - > ps . delta_angles [ ROLL ] ;
/*
if ( doSound )
{
G_Sound ( NPC , G_SoundIndex ( va ( " sound/enemies/borg/borgservo%d.wav " , Q_irand ( 1 , 8 ) ) ) ) ;
}
*/
return exact ;
# endif
}
void NPC_AimWiggle ( vec3_t enemy_org )
{
//shoot for somewhere between the head and torso
//NOTE: yes, I know this looks weird, but it works
if ( NPCInfo - > aimErrorDebounceTime < level . time )
{
NPCInfo - > aimOfs [ 0 ] = 0.3 * Q_flrand ( NPC - > enemy - > mins [ 0 ] , NPC - > enemy - > maxs [ 0 ] ) ;
NPCInfo - > aimOfs [ 1 ] = 0.3 * Q_flrand ( NPC - > enemy - > mins [ 1 ] , NPC - > enemy - > maxs [ 1 ] ) ;
if ( NPC - > enemy - > maxs [ 2 ] > 0 )
{
NPCInfo - > aimOfs [ 2 ] = NPC - > enemy - > maxs [ 2 ] * Q_flrand ( 0.0f , - 1.0f ) ;
}
}
VectorAdd ( enemy_org , NPCInfo - > aimOfs , enemy_org ) ;
}
/*
qboolean NPC_UpdateFiringAngles ( qboolean doPitch , qboolean doYaw )
Includes aim when determining angles - so they don ' t always hit . . .
*/
qboolean NPC_UpdateFiringAngles ( qboolean doPitch , qboolean doYaw )
{
#if 0
float diff ;
float error ;
float targetPitch = 0 ;
float targetYaw = 0 ;
qboolean exact = qtrue ;
if ( level . time < NPCInfo - > aimTime )
{
if ( doPitch )
targetPitch = NPCInfo - > lockedDesiredPitch ;
if ( doYaw )
targetYaw = NPCInfo - > lockedDesiredYaw ;
}
else
{
if ( doPitch )
{
targetPitch = NPCInfo - > desiredPitch ;
NPCInfo - > lockedDesiredPitch = NPCInfo - > desiredPitch ;
}
if ( doYaw )
{
targetYaw = NPCInfo - > desiredYaw ;
NPCInfo - > lockedDesiredYaw = NPCInfo - > desiredYaw ;
}
}
if ( doYaw )
{
// add yaw error based on NPCInfo->aim value
error = ( ( float ) ( 6 - NPCInfo - > stats . aim ) ) * Q_flrand ( - 1 , 1 ) ;
if ( Q_irand ( 0 , 1 ) )
error * = - 1 ;
diff = AngleDelta ( NPC - > client - > ps . viewangles [ YAW ] , targetYaw ) ;
if ( diff )
exact = qfalse ;
ucmd . angles [ YAW ] = ANGLE2SHORT ( targetYaw + diff + error ) - client - > ps . delta_angles [ YAW ] ;
}
if ( doPitch )
{
// add pitch error based on NPCInfo->aim value
error = ( ( float ) ( 6 - NPCInfo - > stats . aim ) ) * Q_flrand ( - 1 , 1 ) ;
diff = AngleDelta ( NPC - > client - > ps . viewangles [ PITCH ] , targetPitch ) ;
if ( diff )
exact = qfalse ;
ucmd . angles [ PITCH ] = ANGLE2SHORT ( targetPitch + diff + error ) - client - > ps . delta_angles [ PITCH ] ;
}
ucmd . angles [ ROLL ] = ANGLE2SHORT ( NPC - > client - > ps . viewangles [ ROLL ] ) - client - > ps . delta_angles [ ROLL ] ;
return exact ;
# else
float error , diff ;
float decay ;
float targetPitch = 0 ;
float targetYaw = 0 ;
qboolean exact = qtrue ;
// if angle changes are locked; just keep the current angles
if ( level . time < NPCInfo - > aimTime )
{
if ( doPitch )
targetPitch = NPCInfo - > lockedDesiredPitch ;
if ( doYaw )
targetYaw = NPCInfo - > lockedDesiredYaw ;
}
else
{
if ( doPitch )
targetPitch = NPCInfo - > desiredPitch ;
if ( doYaw )
targetYaw = NPCInfo - > desiredYaw ;
// NPCInfo->aimTime = level.time + 250;
if ( doPitch )
NPCInfo - > lockedDesiredPitch = NPCInfo - > desiredPitch ;
if ( doYaw )
NPCInfo - > lockedDesiredYaw = NPCInfo - > desiredYaw ;
}
if ( NPCInfo - > aimErrorDebounceTime < level . time )
{
if ( Q_irand ( 0 , 1 ) )
{
NPCInfo - > lastAimErrorYaw = ( ( float ) ( 6 - NPCInfo - > stats . aim ) ) * Q_flrand ( - 1 , 1 ) ;
}
if ( Q_irand ( 0 , 1 ) )
{
NPCInfo - > lastAimErrorPitch = ( ( float ) ( 6 - NPCInfo - > stats . aim ) ) * Q_flrand ( - 1 , 1 ) ;
}
NPCInfo - > aimErrorDebounceTime = level . time + Q_irand ( 250 , 2000 ) ;
}
if ( doYaw )
{
// decay yaw diff
diff = AngleDelta ( NPC - > client - > ps . viewangles [ YAW ] , targetYaw ) ;
if ( diff )
{
exact = qfalse ;
decay = 60.0 + 80.0 ;
decay * = 50.0f / 1000.0f ; //msec
if ( diff < 0.0 )
{
diff + = decay ;
if ( diff > 0.0 )
{
diff = 0.0 ;
}
}
else
{
diff - = decay ;
if ( diff < 0.0 )
{
diff = 0.0 ;
}
}
}
// add yaw error based on NPCInfo->aim value
error = NPCInfo - > lastAimErrorYaw ;
/*
if ( Q_irand ( 0 , 1 ) )
{
error * = - 1 ;
}
*/
ucmd . angles [ YAW ] = ANGLE2SHORT ( targetYaw + diff + error ) - client - > ps . delta_angles [ YAW ] ;
}
if ( doPitch )
{
// decay pitch diff
diff = AngleDelta ( NPC - > client - > ps . viewangles [ PITCH ] , targetPitch ) ;
if ( diff )
{
exact = qfalse ;
decay = 60.0 + 80.0 ;
decay * = 50.0f / 1000.0f ; //msec
if ( diff < 0.0 )
{
diff + = decay ;
if ( diff > 0.0 )
{
diff = 0.0 ;
}
}
else
{
diff - = decay ;
if ( diff < 0.0 )
{
diff = 0.0 ;
}
}
}
error = NPCInfo - > lastAimErrorPitch ;
ucmd . angles [ PITCH ] = ANGLE2SHORT ( targetPitch + diff + error ) - client - > ps . delta_angles [ PITCH ] ;
}
ucmd . angles [ ROLL ] = ANGLE2SHORT ( NPC - > client - > ps . viewangles [ ROLL ] ) - client - > ps . delta_angles [ ROLL ] ;
return exact ;
# endif
}
//===================================================================================
/*
static void NPC_UpdateShootAngles ( vec3_t angles , qboolean doPitch , qboolean doYaw )
Does update angles on shootAngles
*/
void NPC_UpdateShootAngles ( vec3_t angles , qboolean doPitch , qboolean doYaw )
{ //FIXME: shoot angles either not set right or not used!
float error ;
float decay ;
float targetPitch = 0 ;
float targetYaw = 0 ;
if ( doPitch )
targetPitch = angles [ PITCH ] ;
if ( doYaw )
targetYaw = angles [ YAW ] ;
if ( doYaw )
{
// decay yaw error
error = AngleDelta ( NPCInfo - > shootAngles [ YAW ] , targetYaw ) ;
if ( error )
{
decay = 60.0 + 80.0 * NPCInfo - > stats . aim ;
decay * = 100.0f / 1000.0f ; //msec
if ( error < 0.0 )
{
error + = decay ;
if ( error > 0.0 )
{
error = 0.0 ;
}
}
else
{
error - = decay ;
if ( error < 0.0 )
{
error = 0.0 ;
}
}
}
NPCInfo - > shootAngles [ YAW ] = targetYaw + error ;
}
if ( doPitch )
{
// decay pitch error
error = AngleDelta ( NPCInfo - > shootAngles [ PITCH ] , targetPitch ) ;
if ( error )
{
decay = 60.0 + 80.0 * NPCInfo - > stats . aim ;
decay * = 100.0f / 1000.0f ; //msec
if ( error < 0.0 )
{
error + = decay ;
if ( error > 0.0 )
{
error = 0.0 ;
}
}
else
{
error - = decay ;
if ( error < 0.0 )
{
error = 0.0 ;
}
}
}
NPCInfo - > shootAngles [ PITCH ] = targetPitch + error ;
}
}
/*
void SetTeamNumbers ( void )
Sets the number of living clients on each team
FIXME : Does not account for non - respawned players !
FIXME : Don ' t include medics ?
*/
void SetTeamNumbers ( void )
{
gentity_t * found ;
int i ;
for ( i = 0 ; i < TEAM_NUM_TEAMS ; i + + )
{
teamNumbers [ i ] = 0 ;
teamStrength [ i ] = 0 ;
}
for ( i = 0 ; i < 1 ; i + + )
{
found = & g_entities [ i ] ;
if ( found - > client )
{
if ( found - > health > 0 ) //FIXME: or if a player!
{
teamNumbers [ found - > client - > playerTeam ] + + ;
teamStrength [ found - > client - > playerTeam ] + = found - > health ;
}
}
}
for ( i = 0 ; i < TEAM_NUM_TEAMS ; i + + )
{ //Get the average health
teamStrength [ i ] = floor ( ( ( float ) ( teamStrength [ i ] ) ) / ( ( float ) ( teamNumbers [ i ] ) ) ) ;
}
}
extern stringID_table_t BSTable [ ] ;
extern stringID_table_t BSETTable [ ] ;
qboolean G_ActivateBehavior ( gentity_t * self , int bset )
{
bState_t bSID = ( bState_t ) - 1 ;
char * bs_name = NULL ;
if ( ! self )
{
return qfalse ;
}
bs_name = self - > behaviorSet [ bset ] ;
if ( ! ( VALIDSTRING ( bs_name ) ) )
{
return qfalse ;
}
if ( self - > NPC )
{
bSID = ( bState_t ) ( GetIDForString ( BSTable , bs_name ) ) ;
}
if ( bSID ! = ( bState_t ) - 1 )
{
self - > NPC - > tempBehavior = BS_DEFAULT ;
self - > NPC - > behaviorState = bSID ;
if ( bSID = = BS_SEARCH | | bSID = = BS_WANDER )
{
//FIXME: Reimplement?
if ( self - > waypoint ! = WAYPOINT_NONE )
{
NPC_BSSearchStart ( self - > waypoint , bSID ) ;
}
else
{
self - > waypoint = NAV : : GetNearestNode ( self ) ;
if ( self - > waypoint ! = WAYPOINT_NONE )
{
NPC_BSSearchStart ( self - > waypoint , bSID ) ;
}
}
}
}
else
{
Quake3Game ( ) - > DebugPrint ( IGameInterface : : WL_VERBOSE , " %s attempting to run bSet %s (%s) \n " , self - > targetname , GetStringForID ( BSETTable , bset ) , bs_name ) ;
Quake3Game ( ) - > RunScript ( self , bs_name ) ;
}
return qtrue ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Extended Functions
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_ValidEnemy
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
qboolean G_ValidEnemy ( gentity_t * self , gentity_t * enemy )
{
//Must be a valid pointer
if ( enemy = = NULL )
return qfalse ;
//Must not be me
if ( enemy = = self )
return qfalse ;
//Must not be deleted
if ( enemy - > inuse = = qfalse )
return qfalse ;
//Must be alive
if ( enemy - > health < = 0 )
return qfalse ;
//In case they're in notarget mode
if ( enemy - > flags & FL_NOTARGET )
return qfalse ;
//Must be an NPC
if ( enemy - > client = = NULL )
{
if ( enemy - > svFlags & SVF_NONNPC_ENEMY )
{ //still potentially valid
if ( self - > client )
{
if ( enemy - > noDamageTeam = = self - > client - > playerTeam )
{
return qfalse ;
}
else
{
return qtrue ;
}
}
else
{
if ( enemy - > noDamageTeam = = self - > noDamageTeam )
{
return qfalse ;
}
else
{
return qtrue ;
}
}
}
else
{
return qfalse ;
}
}
if ( enemy - > client - > playerTeam = = TEAM_FREE & & enemy - > s . number < MAX_CLIENTS )
{ //An evil player, everyone attacks him
return qtrue ;
}
//Can't be on the same team
if ( enemy - > client - > playerTeam = = self - > client - > playerTeam )
{
return qfalse ;
}
//if haven't seen him in a while, give up
//if ( NPCInfo->enemyLastSeenTime != 0 && level.time - NPCInfo->enemyLastSeenTime > 7000 )//FIXME: make a stat?
// return qfalse;
if ( enemy - > client - > playerTeam = = self - > client - > enemyTeam //simplest case: they're on my enemy team
| | ( self - > client - > enemyTeam = = TEAM_FREE & & enemy - > client - > NPC_class ! = self - > client - > NPC_class ) //I get mad at anyone and this guy isn't the same class as me
| | ( enemy - > client - > NPC_class = = CLASS_WAMPA & & enemy - > enemy ) //a rampaging wampa
| | ( enemy - > client - > NPC_class = = CLASS_RANCOR & & enemy - > enemy ) //a rampaging rancor
| | ( enemy - > client - > playerTeam = = TEAM_FREE & & enemy - > client - > enemyTeam = = TEAM_FREE & & enemy - > enemy & & enemy - > enemy - > client & & ( enemy - > enemy - > client - > playerTeam = = self - > client - > playerTeam | | ( enemy - > enemy - > client - > playerTeam ! = TEAM_ENEMY & & self - > client - > playerTeam = = TEAM_PLAYER ) ) ) //enemy is a rampaging non-aligned creature who is attacking someone on our team or a non-enemy (this last condition is used only if we're a good guy - in effect, we protect the innocent)
)
{
return qtrue ;
}
//all other cases = false?
return qfalse ;
}
qboolean NPC_ValidEnemy ( gentity_t * ent )
{
return G_ValidEnemy ( NPC , ent ) ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_TargetVisible
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
qboolean NPC_TargetVisible ( gentity_t * ent )
{
//Make sure we're in a valid range
if ( DistanceSquared ( ent - > currentOrigin , NPC - > currentOrigin ) > ( NPCInfo - > stats . visrange * NPCInfo - > stats . visrange ) )
return qfalse ;
//Check our FOV
if ( InFOV ( ent , NPC , NPCInfo - > stats . hfov , NPCInfo - > stats . vfov ) = = qfalse )
return qfalse ;
//Check for sight
if ( NPC_ClearLOS ( ent ) = = qfalse )
return qfalse ;
return qtrue ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_GetCheckDelta
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
/*
# define CHECK_TIME_BASE 250
# define CHECK_TIME_BASE_SQUARED ( CHECK_TIME_BASE * CHECK_TIME_BASE )
static int NPC_GetCheckDelta ( void )
{
if ( NPC_ValidEnemy ( NPC - > enemy ) = = qfalse )
{
int distance = DistanceSquared ( NPC - > currentOrigin , g_entities [ 0 ] . currentOrigin ) ;
distance / = CHECK_TIME_BASE_SQUARED ;
return ( CHECK_TIME_BASE * distance ) ;
}
return 0 ;
}
*/
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_FindNearestEnemy
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
# define MAX_RADIUS_ENTS 256 //NOTE: This can cause entities to be lost
# define NEAR_DEFAULT_RADIUS 256
extern gentity_t * G_CheckControlledTurretEnemy ( gentity_t * self , gentity_t * enemy , qboolean validate ) ;
int NPC_FindNearestEnemy ( gentity_t * ent )
{
gentity_t * radiusEnts [ MAX_RADIUS_ENTS ] ;
gentity_t * nearest ;
vec3_t mins , maxs ;
int nearestEntID = - 1 ;
float nearestDist = ( float ) WORLD_SIZE * ( float ) WORLD_SIZE ;
float distance ;
int numEnts , numChecks = 0 ;
int i ;
//Setup the bbox to search in
for ( i = 0 ; i < 3 ; i + + )
{
mins [ i ] = ent - > currentOrigin [ i ] - NPCInfo - > stats . visrange ;
maxs [ i ] = ent - > currentOrigin [ i ] + NPCInfo - > stats . visrange ;
}
//Get a number of entities in a given space
numEnts = gi . EntitiesInBox ( mins , maxs , radiusEnts , MAX_RADIUS_ENTS ) ;
for ( i = 0 ; i < numEnts ; i + + )
{
nearest = G_CheckControlledTurretEnemy ( ent , radiusEnts [ i ] , qtrue ) ;
//Don't consider self
if ( nearest = = ent )
continue ;
//Must be valid
if ( NPC_ValidEnemy ( nearest ) = = qfalse )
continue ;
numChecks + + ;
//Must be visible
if ( NPC_TargetVisible ( nearest ) = = qfalse )
continue ;
distance = DistanceSquared ( ent - > currentOrigin , nearest - > currentOrigin ) ;
//Found one closer to us
if ( distance < nearestDist )
{
nearestEntID = nearest - > s . number ;
nearestDist = distance ;
}
}
return nearestEntID ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_PickEnemyExt
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
gentity_t * NPC_PickEnemyExt ( qboolean checkAlerts = qfalse )
{
//If we've asked for the closest enemy
int entID = NPC_FindNearestEnemy ( NPC ) ;
//If we have a valid enemy, use it
if ( entID > = 0 )
return & g_entities [ entID ] ;
if ( checkAlerts )
{
int alertEvent = NPC_CheckAlertEvents ( qtrue , qtrue , - 1 , qtrue , AEL_DISCOVERED ) ;
//There is an event to look at
if ( alertEvent > = 0 )
{
alertEvent_t * event = & level . alertEvents [ alertEvent ] ;
//Don't pay attention to our own alerts
if ( event - > owner = = NPC )
return NULL ;
if ( event - > level > = AEL_DISCOVERED )
{
//If it's the player, attack him
if ( event - > owner = = & g_entities [ 0 ] )
return event - > owner ;
//If it's on our team, then take its enemy as well
if ( ( event - > owner - > client ) & & ( event - > owner - > client - > playerTeam = = NPC - > client - > playerTeam ) )
return event - > owner - > enemy ;
}
}
}
return NULL ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_FindPlayer
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
qboolean NPC_FindPlayer ( void )
{
return NPC_TargetVisible ( & g_entities [ 0 ] ) ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_CheckPlayerDistance
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
static qboolean NPC_CheckPlayerDistance ( void )
{
//Make sure we have an enemy
if ( NPC - > enemy = = NULL )
return qfalse ;
//Only do this for non-players
if ( NPC - > enemy - > s . number = = 0 )
return qfalse ;
//must be set up to get mad at player
if ( ! NPC - > client | | NPC - > client - > enemyTeam ! = TEAM_PLAYER )
return qfalse ;
//Must be within our FOV
if ( InFOV ( & g_entities [ 0 ] , NPC , NPCInfo - > stats . hfov , NPCInfo - > stats . vfov ) = = qfalse )
return qfalse ;
float distance = DistanceSquared ( NPC - > currentOrigin , NPC - > enemy - > currentOrigin ) ;
if ( distance > DistanceSquared ( NPC - > currentOrigin , g_entities [ 0 ] . currentOrigin ) )
{
G_SetEnemy ( NPC , & g_entities [ 0 ] ) ;
return qtrue ;
}
return qfalse ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_FindEnemy
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
qboolean NPC_FindEnemy ( qboolean checkAlerts = qfalse )
{
//We're ignoring all enemies for now
if ( NPC - > svFlags & SVF_IGNORE_ENEMIES )
{
G_ClearEnemy ( NPC ) ;
return qfalse ;
}
//we can't pick up any enemies for now
if ( NPCInfo - > confusionTime > level . time )
{
G_ClearEnemy ( NPC ) ;
return qfalse ;
}
//Don't want a new enemy
if ( ( NPC_ValidEnemy ( NPC - > enemy ) ) & & ( NPC - > svFlags & SVF_LOCKEDENEMY ) )
return qtrue ;
//See if the player is closer than our current enemy
if ( NPC - > client - > NPC_class ! = CLASS_RANCOR
& & NPC - > client - > NPC_class ! = CLASS_WAMPA
& & NPC - > client - > NPC_class ! = CLASS_SAND_CREATURE
& & NPC_CheckPlayerDistance ( ) )
{ //rancors, wampas & sand creatures don't care if player is closer, they always go with closest
return qtrue ;
}
//Otherwise, turn off the flag
NPC - > svFlags & = ~ SVF_LOCKEDENEMY ;
//If we've gotten here alright, then our target it still valid
if ( NPC_ValidEnemy ( NPC - > enemy ) )
return qtrue ;
gentity_t * newenemy = NPC_PickEnemyExt ( checkAlerts ) ;
//if we found one, take it as the enemy
if ( NPC_ValidEnemy ( newenemy ) )
{
G_SetEnemy ( NPC , newenemy ) ;
return qtrue ;
}
G_ClearEnemy ( NPC ) ;
return qfalse ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_CheckEnemyExt
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
qboolean NPC_CheckEnemyExt ( qboolean checkAlerts )
{
//Make sure we're ready to think again
/*
if ( NPCInfo - > enemyCheckDebounceTime > level . time )
return qfalse ;
//Get our next think time
NPCInfo - > enemyCheckDebounceTime = level . time + NPC_GetCheckDelta ( ) ;
//Attempt to find an enemy
return NPC_FindEnemy ( ) ;
*/
return NPC_FindEnemy ( checkAlerts ) ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_FacePosition
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
qboolean NPC_FacePosition ( vec3_t position , qboolean doPitch )
{
vec3_t muzzle ;
qboolean facing = qtrue ;
//Get the positions
if ( NPC - > client & & ( NPC - > client - > NPC_class = = CLASS_RANCOR | | NPC - > client - > NPC_class = = CLASS_WAMPA | | NPC - > client - > NPC_class = = CLASS_SAND_CREATURE ) )
{
CalcEntitySpot ( NPC , SPOT_ORIGIN , muzzle ) ;
muzzle [ 2 ] + = NPC - > maxs [ 2 ] * 0.75f ;
}
else if ( NPC - > client & & NPC - > client - > NPC_class = = CLASS_GALAKMECH )
{
CalcEntitySpot ( NPC , SPOT_WEAPON , muzzle ) ;
}
else
{
CalcEntitySpot ( NPC , SPOT_HEAD_LEAN , muzzle ) ; //SPOT_HEAD
if ( NPC - > client - > NPC_class = = CLASS_ROCKETTROOPER )
{ //*sigh*, look down more
position [ 2 ] - = 32 ;
}
}
//Find the desired angles
vec3_t angles ;
GetAnglesForDirection ( muzzle , position , angles ) ;
NPCInfo - > desiredYaw = AngleNormalize360 ( angles [ YAW ] ) ;
NPCInfo - > desiredPitch = AngleNormalize360 ( angles [ PITCH ] ) ;
if ( NPC - > enemy & & NPC - > enemy - > client & & NPC - > enemy - > client - > NPC_class = = CLASS_ATST )
{
// FIXME: this is kind of dumb, but it was the easiest way to get it to look sort of ok
NPCInfo - > desiredYaw + = Q_flrand ( - 5 , 5 ) + sin ( level . time * 0.004f ) * 7 ;
NPCInfo - > desiredPitch + = Q_flrand ( - 2 , 2 ) ;
}
//Face that yaw
NPC_UpdateAngles ( qtrue , qtrue ) ;
//Find the delta between our goal and our current facing
float yawDelta = AngleNormalize360 ( NPCInfo - > desiredYaw - ( SHORT2ANGLE ( ucmd . angles [ YAW ] + client - > ps . delta_angles [ YAW ] ) ) ) ;
//See if we are facing properly
if ( fabs ( yawDelta ) > VALID_ATTACK_CONE )
facing = qfalse ;
if ( doPitch )
{
//Find the delta between our goal and our current facing
float currentAngles = ( SHORT2ANGLE ( ucmd . angles [ PITCH ] + client - > ps . delta_angles [ PITCH ] ) ) ;
float pitchDelta = NPCInfo - > desiredPitch - currentAngles ;
//See if we are facing properly
if ( fabs ( pitchDelta ) > VALID_ATTACK_CONE )
facing = qfalse ;
}
return facing ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_FaceEntity
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
qboolean NPC_FaceEntity ( gentity_t * ent , qboolean doPitch )
{
vec3_t entPos ;
//Get the positions
CalcEntitySpot ( ent , SPOT_HEAD_LEAN , entPos ) ;
return NPC_FacePosition ( entPos , doPitch ) ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_FaceEnemy
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
qboolean NPC_FaceEnemy ( qboolean doPitch )
{
if ( NPC = = NULL )
return qfalse ;
if ( NPC - > enemy = = NULL )
return qfalse ;
return NPC_FaceEntity ( NPC - > enemy , doPitch ) ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_CheckCanAttackExt
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
qboolean NPC_CheckCanAttackExt ( void )
{
//We don't want them to shoot
if ( NPCInfo - > scriptFlags & SCF_DONT_FIRE )
return qfalse ;
//Turn to face
if ( NPC_FaceEnemy ( qtrue ) = = qfalse )
return qfalse ;
//Must have a clear line of sight to the target
if ( NPC_ClearShot ( NPC - > enemy ) = = qfalse )
return qfalse ;
return qtrue ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_ClearLookTarget
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
void NPC_ClearLookTarget ( gentity_t * self )
{
if ( ! self - > client )
{
return ;
}
self - > client - > renderInfo . lookTarget = ENTITYNUM_NONE ; //ENTITYNUM_WORLD;
self - > client - > renderInfo . lookTargetClearTime = 0 ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_SetLookTarget
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
void NPC_SetLookTarget ( gentity_t * self , int entNum , int clearTime )
{
if ( ! self - > client )
{
return ;
}
self - > client - > renderInfo . lookTarget = entNum ;
self - > client - > renderInfo . lookTargetClearTime = clearTime ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_CheckLookTarget
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
qboolean NPC_CheckLookTarget ( gentity_t * self )
{
if ( self - > client )
{
if ( self - > client - > renderInfo . lookTarget > = 0 & & self - > client - > renderInfo . lookTarget < ENTITYNUM_WORLD )
{ //within valid range
if ( ( & g_entities [ self - > client - > renderInfo . lookTarget ] = = NULL ) | | ! g_entities [ self - > client - > renderInfo . lookTarget ] . inuse )
{ //lookTarget not inuse or not valid anymore
NPC_ClearLookTarget ( self ) ;
}
else if ( self - > client - > renderInfo . lookTargetClearTime & & self - > client - > renderInfo . lookTargetClearTime < level . time )
{ //Time to clear lookTarget
NPC_ClearLookTarget ( self ) ;
}
else if ( g_entities [ self - > client - > renderInfo . lookTarget ] . client & & self - > enemy & & ( & g_entities [ self - > client - > renderInfo . lookTarget ] ! = self - > enemy ) )
{ //should always look at current enemy if engaged in battle... FIXME: this could override certain scripted lookTargets...???
NPC_ClearLookTarget ( self ) ;
}
else
{
return qtrue ;
}
}
}
return qfalse ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_CheckCharmed
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
extern void G_AddVoiceEvent ( gentity_t * self , int event , int speakDebounceTime ) ;
void G_CheckCharmed ( gentity_t * self )
{
if ( self
& & self - > client
& & self - > client - > playerTeam = = TEAM_PLAYER
& & self - > NPC
& & self - > NPC - > charmedTime
& & ( self - > NPC - > charmedTime < level . time | | self - > health < = 0 ) )
{ //we were charmed, set us back!
//NOTE: presumptions here...
team_t savTeam = self - > client - > enemyTeam ;
self - > client - > enemyTeam = self - > client - > playerTeam ;
self - > client - > playerTeam = savTeam ;
self - > client - > leader = NULL ;
self - > NPC - > charmedTime = 0 ;
if ( self - > health > 0 )
{
if ( self - > NPC - > tempBehavior = = BS_FOLLOW_LEADER )
{
self - > NPC - > tempBehavior = BS_DEFAULT ;
}
G_ClearEnemy ( self ) ;
//say something to let player know you've snapped out of it
G_AddVoiceEvent ( self , Q_irand ( EV_CONFUSE1 , EV_CONFUSE3 ) , 2000 ) ;
}
}
}
void G_GetBoltPosition ( gentity_t * self , int boltIndex , vec3_t pos , int modelIndex = 0 )
{
if ( ! self | | ! self - > ghoul2 . size ( ) )
{
return ;
}
mdxaBone_t boltMatrix ;
vec3_t result , angles = { 0 , self - > currentAngles [ YAW ] , 0 } ;
gi . G2API_GetBoltMatrix ( self - > ghoul2 , modelIndex ,
boltIndex ,
& boltMatrix , angles , self - > currentOrigin , ( cg . time ? cg . time : level . time ) ,
NULL , self - > s . modelScale ) ;
if ( pos )
{
gi . G2API_GiveMeVectorFromMatrix ( boltMatrix , ORIGIN , result ) ;
VectorCopy ( result , pos ) ;
}
}
float NPC_EntRangeFromBolt ( gentity_t * targEnt , int boltIndex )
{
vec3_t org = { 0.0f } ;
if ( ! targEnt )
{
return Q3_INFINITE ;
}
G_GetBoltPosition ( NPC , boltIndex , org ) ;
return ( Distance ( targEnt - > currentOrigin , org ) ) ;
}
float NPC_EnemyRangeFromBolt ( int boltIndex )
{
return ( NPC_EntRangeFromBolt ( NPC - > enemy , boltIndex ) ) ;
}
int G_GetEntsNearBolt ( gentity_t * self , gentity_t * * radiusEnts , float radius , int boltIndex , vec3_t boltOrg )
{
vec3_t mins , maxs ;
int i ;
//get my handRBolt's position
vec3_t org = { 0.0f } ;
G_GetBoltPosition ( self , boltIndex , org ) ;
VectorCopy ( org , boltOrg ) ;
//Setup the bbox to search in
for ( i = 0 ; i < 3 ; i + + )
{
mins [ i ] = boltOrg [ i ] - radius ;
maxs [ i ] = boltOrg [ i ] + radius ;
}
//Get the number of entities in a given space
return ( gi . EntitiesInBox ( mins , maxs , radiusEnts , 128 ) ) ;
}
int NPC_GetEntsNearBolt ( gentity_t * * radiusEnts , float radius , int boltIndex , vec3_t boltOrg )
{
return ( G_GetEntsNearBolt ( NPC , radiusEnts , radius , boltIndex , boltOrg ) ) ;
}
extern qboolean RT_Flying ( gentity_t * self ) ;
extern void RT_FlyStart ( gentity_t * self ) ;
extern void RT_FlyStop ( gentity_t * self ) ;
extern qboolean Boba_Flying ( gentity_t * self ) ;
extern void Boba_FlyStart ( gentity_t * self ) ;
extern void Boba_FlyStop ( gentity_t * self ) ;
qboolean JET_Flying ( gentity_t * self )
{
if ( ! self | | ! self - > client )
{
return qfalse ;
}
if ( self - > client - > NPC_class = = CLASS_BOBAFETT )
{
return ( Boba_Flying ( self ) ) ;
}
else if ( self - > client - > NPC_class = = CLASS_ROCKETTROOPER )
{
return ( RT_Flying ( self ) ) ;
}
else
{
return qfalse ;
}
}
void JET_FlyStart ( gentity_t * self )
{
if ( ! self | | ! self - > client )
{
return ;
}
self - > lastInAirTime = level . time ;
if ( self - > client - > NPC_class = = CLASS_BOBAFETT )
{
Boba_FlyStart ( self ) ;
}
else if ( self - > client - > NPC_class = = CLASS_ROCKETTROOPER )
{
RT_FlyStart ( self ) ;
}
}
void JET_FlyStop ( gentity_t * self )
{
if ( ! self | | ! self - > client )
{
return ;
}
if ( self - > client - > NPC_class = = CLASS_BOBAFETT )
{
Boba_FlyStop ( self ) ;
}
else if ( self - > client - > NPC_class = = CLASS_ROCKETTROOPER )
{
RT_FlyStop ( self ) ;
}
}