2013-04-04 14:52:42 +00:00
// leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
# include "g_headers.h"
# include "b_local.h"
# include "g_nav.h"
# include "anims.h"
# include "wp_saber.h"
extern void G_AddVoiceEvent ( gentity_t * self , int event , int speakDebounceTime ) ;
extern qboolean Q3_TaskIDPending ( gentity_t * ent , taskID_t taskType ) ;
extern void NPC_AimAdjust ( int change ) ;
extern qboolean WP_LobFire ( gentity_t * self , vec3_t start , vec3_t target , vec3_t mins , vec3_t maxs , int clipmask ,
vec3_t velocity , qboolean tracePath , int ignoreEntNum , int enemyNum ,
float minSpeed , float maxSpeed , float idealSpeed , qboolean mustHit ) ;
extern qboolean InFront ( vec3_t spot , vec3_t from , vec3_t fromAngles , float threshHold = 0.0f ) ;
extern void G_SoundAtSpot ( vec3_t org , int soundIndex ) ;
extern void G_SoundOnEnt ( gentity_t * ent , soundChannel_t channel , const char * soundPath ) ;
extern qboolean PM_CrouchAnim ( int anim ) ;
# define MELEE_DIST_SQUARED 6400 //80*80
# define MIN_LOB_DIST_SQUARED 65536 //256*256
# define MAX_LOB_DIST_SQUARED 200704 //448*448
# define REPEATER_ALT_SIZE 3 // half of bbox size
# define GENERATOR_HEALTH 25
# define TURN_ON 0x00000000
# define TURN_OFF 0x00000100
# define GALAK_SHIELD_HEALTH 500
static vec3_t shieldMins = { - 60 , - 60 , - 24 } ;
static vec3_t shieldMaxs = { 60 , 60 , 80 } ;
extern qboolean NPC_CheckPlayerTeamStealth ( void ) ;
static qboolean enemyLOS ;
static qboolean enemyCS ;
static qboolean hitAlly ;
static qboolean faceEnemy ;
static qboolean move ;
static qboolean shoot ;
static float enemyDist ;
static vec3_t impactPos ;
void NPC_GalakMech_Precache ( void )
{
G_SoundIndex ( " sound/weapons/galak/skewerhit.wav " ) ;
G_SoundIndex ( " sound/weapons/galak/lasercharge.wav " ) ;
G_SoundIndex ( " sound/weapons/galak/lasercutting.wav " ) ;
G_SoundIndex ( " sound/weapons/galak/lasercutting.wav " ) ;
G_SoundIndex ( " sound/weapons/galak/laserdamage.wav " ) ;
G_SoundIndex ( " sound/weapons/galak/laserdamage.wav " ) ;
G_EffectIndex ( " galak/trace_beam " ) ;
G_EffectIndex ( " galak/beam_warmup " ) ;
G_EffectIndex ( " small_chunks " ) ;
G_EffectIndex ( " mouseexplosion1 " ) ;
G_EffectIndex ( " probeexplosion1 " ) ;
G_EffectIndex ( " blaster/smoke_bolton " ) ;
G_EffectIndex ( " env/exp_trail_comp " ) ;
}
void NPC_GalakMech_Init ( gentity_t * ent )
{
if ( ent - > NPC - > behaviorState ! = BS_CINEMATIC )
{
ent - > client - > ps . stats [ STAT_ARMOR ] = GALAK_SHIELD_HEALTH ;
ent - > NPC - > investigateCount = ent - > NPC - > investigateDebounceTime = 0 ;
ent - > flags | = FL_SHIELDED ; //reflect normal shots
ent - > client - > ps . powerups [ PW_GALAK_SHIELD ] = Q3_INFINITE ; //temp, for effect
ent - > fx_time = level . time ;
VectorSet ( ent - > mins , - 60 , - 60 , - 24 ) ;
VectorSet ( ent - > maxs , 60 , 60 , 80 ) ;
ent - > flags | = FL_NO_KNOCKBACK ; //don't get pushed
TIMER_Set ( ent , " attackDelay " , 0 ) ; //FIXME: Slant for difficulty levels
TIMER_Set ( ent , " flee " , 0 ) ;
TIMER_Set ( ent , " smackTime " , 0 ) ;
TIMER_Set ( ent , " beamDelay " , 0 ) ;
TIMER_Set ( ent , " noLob " , 0 ) ;
TIMER_Set ( ent , " noRapid " , 0 ) ;
}
else
{
// gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "helmet", TURN_OFF );
2013-04-04 18:02:27 +00:00
gi . G2API_SetSurfaceOnOff ( & ent - > ghoul2 [ ent - > playerModel ] , " torso_shield_off " , TURN_OFF ) ;
2013-04-04 14:52:42 +00:00
gi . G2API_SetSurfaceOnOff ( & ent - > ghoul2 [ ent - > playerModel ] , " torso_galakface_off " , TURN_ON ) ;
gi . G2API_SetSurfaceOnOff ( & ent - > ghoul2 [ ent - > playerModel ] , " torso_galakhead_off " , TURN_ON ) ;
gi . G2API_SetSurfaceOnOff ( & ent - > ghoul2 [ ent - > playerModel ] , " torso_eyes_mouth_off " , TURN_ON ) ;
gi . G2API_SetSurfaceOnOff ( & ent - > ghoul2 [ ent - > playerModel ] , " torso_collar_off " , TURN_ON ) ;
gi . G2API_SetSurfaceOnOff ( & ent - > ghoul2 [ ent - > playerModel ] , " torso_galaktorso_off " , TURN_ON ) ;
}
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
GM_Dying
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
extern void NPC_Mark1_Part_Explode ( gentity_t * self , int bolt ) ;
void GM_Dying ( gentity_t * self )
{
if ( level . time - self - > s . time < 10000 )
{ //FIXME: need a real effect
if ( TIMER_Done ( self , " dyingExplosion " ) )
{
int newBolt ;
switch ( Q_irand ( 1 , 14 ) )
{
// Find place to generate explosion
case 1 :
if ( ! gi . G2API_GetSurfaceRenderStatus ( & self - > ghoul2 [ self - > playerModel ] , " r_hand " ) )
{ //r_hand still there
NPC_Mark1_Part_Explode ( self , self - > handRBolt ) ;
gi . G2API_SetSurfaceOnOff ( & self - > ghoul2 [ self - > playerModel ] , " r_hand " , TURN_OFF ) ;
}
else if ( ! gi . G2API_GetSurfaceRenderStatus ( & self - > ghoul2 [ self - > playerModel ] , " r_arm_middle " ) )
{ //r_arm_middle still there
newBolt = gi . G2API_AddBolt ( & self - > ghoul2 [ self - > playerModel ] , " *r_arm_elbow " ) ;
gi . G2API_SetSurfaceOnOff ( & self - > ghoul2 [ self - > playerModel ] , " r_arm_middle " , TURN_OFF ) ;
}
break ;
case 2 :
//FIXME: do only once?
if ( ! gi . G2API_GetSurfaceRenderStatus ( & self - > ghoul2 [ self - > playerModel ] , " l_hand " ) )
{ //l_hand still there
NPC_Mark1_Part_Explode ( self , self - > handLBolt ) ;
gi . G2API_SetSurfaceOnOff ( & self - > ghoul2 [ self - > playerModel ] , " l_hand " , TURN_OFF ) ;
}
else if ( ! gi . G2API_GetSurfaceRenderStatus ( & self - > ghoul2 [ self - > playerModel ] , " l_arm_wrist " ) )
{ //l_arm_wrist still there
newBolt = gi . G2API_AddBolt ( & self - > ghoul2 [ self - > playerModel ] , " *l_arm_cap_l_hand " ) ;
gi . G2API_SetSurfaceOnOff ( & self - > ghoul2 [ self - > playerModel ] , " l_arm_wrist " , TURN_OFF ) ;
}
else if ( ! gi . G2API_GetSurfaceRenderStatus ( & self - > ghoul2 [ self - > playerModel ] , " l_arm_middle " ) )
{ //l_arm_middle still there
newBolt = gi . G2API_AddBolt ( & self - > ghoul2 [ self - > playerModel ] , " *l_arm_cap_l_hand " ) ;
gi . G2API_SetSurfaceOnOff ( & self - > ghoul2 [ self - > playerModel ] , " l_arm_middle " , TURN_OFF ) ;
}
else if ( ! gi . G2API_GetSurfaceRenderStatus ( & self - > ghoul2 [ self - > playerModel ] , " l_arm_augment " ) )
{ //l_arm_augment still there
newBolt = gi . G2API_AddBolt ( & self - > ghoul2 [ self - > playerModel ] , " *l_arm_elbow " ) ;
gi . G2API_SetSurfaceOnOff ( & self - > ghoul2 [ self - > playerModel ] , " l_arm_augment " , TURN_OFF ) ;
}
break ;
case 3 :
case 4 :
newBolt = gi . G2API_AddBolt ( & self - > ghoul2 [ self - > playerModel ] , " *hip_fr " ) ;
NPC_Mark1_Part_Explode ( self , newBolt ) ;
break ;
case 5 :
case 6 :
newBolt = gi . G2API_AddBolt ( & self - > ghoul2 [ self - > playerModel ] , " *shldr_l " ) ;
NPC_Mark1_Part_Explode ( self , newBolt ) ;
break ;
case 7 :
case 8 :
newBolt = gi . G2API_AddBolt ( & self - > ghoul2 [ self - > playerModel ] , " *uchest_r " ) ;
NPC_Mark1_Part_Explode ( self , newBolt ) ;
break ;
case 9 :
case 10 :
NPC_Mark1_Part_Explode ( self , self - > headBolt ) ;
break ;
case 11 :
newBolt = gi . G2API_AddBolt ( & self - > ghoul2 [ self - > playerModel ] , " *l_leg_knee " ) ;
NPC_Mark1_Part_Explode ( self , newBolt ) ;
break ;
case 12 :
newBolt = gi . G2API_AddBolt ( & self - > ghoul2 [ self - > playerModel ] , " *r_leg_knee " ) ;
NPC_Mark1_Part_Explode ( self , newBolt ) ;
break ;
case 13 :
newBolt = gi . G2API_AddBolt ( & self - > ghoul2 [ self - > playerModel ] , " *l_leg_foot " ) ;
NPC_Mark1_Part_Explode ( self , newBolt ) ;
break ;
case 14 :
newBolt = gi . G2API_AddBolt ( & self - > ghoul2 [ self - > playerModel ] , " *r_leg_foot " ) ;
NPC_Mark1_Part_Explode ( self , newBolt ) ;
break ;
}
TIMER_Set ( self , " dyingExplosion " , Q_irand ( 100 , 600 ) ) ;
}
}
else
{ //one final, huge explosion
G_PlayEffect ( " probeexplosion1 " , self - > currentOrigin ) ;
G_PlayEffect ( " small_chunks " , self - > currentOrigin ) ;
G_PlayEffect ( " env/exp_trail_comp " , self - > currentOrigin , self - > currentAngles ) ;
self - > nextthink = level . time + FRAMETIME ;
self - > e_ThinkFunc = thinkF_G_FreeEntity ;
}
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_GM_Pain
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
void NPC_GM_Pain ( gentity_t * self , gentity_t * inflictor , gentity_t * attacker , vec3_t point , int damage , int mod , int hitLoc )
{
if ( self - > client - > ps . powerups [ PW_GALAK_SHIELD ] = = 0 )
{ //shield is currently down
//FIXME: allow for radius damage?
if ( ( hitLoc = = HL_GENERIC1 ) & & ( self - > locationDamage [ HL_GENERIC1 ] > GENERATOR_HEALTH ) )
{
int newBolt = gi . G2API_AddBolt ( & self - > ghoul2 [ self - > playerModel ] , " *antenna_base " ) ;
if ( newBolt ! = - 1 )
{
NPC_Mark1_Part_Explode ( self , newBolt ) ;
}
2013-04-04 18:02:27 +00:00
gi . G2API_SetSurfaceOnOff ( & self - > ghoul2 [ self - > playerModel ] , " torso_shield_off " , TURN_OFF ) ;
2013-04-04 14:52:42 +00:00
gi . G2API_SetSurfaceOnOff ( & self - > ghoul2 [ self - > playerModel ] , " torso_antenna " , TURN_OFF ) ;
gi . G2API_SetSurfaceOnOff ( & self - > ghoul2 [ self - > playerModel ] , " torso_antenna_base_cap_off " , TURN_ON ) ;
self - > client - > ps . powerups [ PW_GALAK_SHIELD ] = 0 ; //temp, for effect
self - > client - > ps . stats [ STAT_ARMOR ] = 0 ; //no more armor
self - > NPC - > investigateDebounceTime = 0 ; //stop recharging
NPC_SetAnim ( self , SETANIM_BOTH , BOTH_ALERT1 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
TIMER_Set ( self , " attackDelay " , self - > client - > ps . torsoAnimTimer ) ;
}
}
else
{ //store the point for shield impact
if ( point )
{
VectorCopy ( point , self - > pos4 ) ;
self - > client - > poisonTime = level . time ;
}
}
if ( ! self - > lockCount & & ! self - > client - > ps . torsoAnimTimer )
{ //don't interrupt laser sweep attack or other special attacks/moves
NPC_Pain ( self , inflictor , attacker , point , damage , mod , hitLoc ) ;
}
if ( inflictor & & inflictor - > lastEnemy = = self )
{ //He force-pushed my own lobfires back at me
if ( mod = = MOD_REPEATER_ALT & & ! Q_irand ( 0 , 2 ) )
{
if ( TIMER_Done ( self , " noRapid " ) )
{
self - > NPC - > scriptFlags & = ~ SCF_ALT_FIRE ;
self - > alt_fire = qfalse ;
TIMER_Set ( self , " noLob " , Q_irand ( 2000 , 6000 ) ) ;
}
else
{ //hopefully this will make us fire the laser
TIMER_Set ( self , " noLob " , Q_irand ( 1000 , 2000 ) ) ;
}
}
else if ( mod = = MOD_REPEATER & & ! Q_irand ( 0 , 5 ) )
{
if ( TIMER_Done ( self , " noLob " ) )
{
self - > NPC - > scriptFlags | = SCF_ALT_FIRE ;
self - > alt_fire = qtrue ;
TIMER_Set ( self , " noRapid " , Q_irand ( 2000 , 6000 ) ) ;
}
else
{ //hopefully this will make us fire the laser
TIMER_Set ( self , " noRapid " , Q_irand ( 1000 , 2000 ) ) ;
}
}
}
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
GM_HoldPosition
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
static void GM_HoldPosition ( void )
{
NPC_FreeCombatPoint ( NPCInfo - > combatPoint , qtrue ) ;
if ( ! Q3_TaskIDPending ( NPC , TID_MOVE_NAV ) )
{ //don't have a script waiting for me to get to my point, okay to stop trying and stand
NPCInfo - > goalEntity = NULL ;
}
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
GM_Move
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
static qboolean GM_Move ( void )
{
NPCInfo - > combatMove = qtrue ; //always move straight toward our goal
qboolean moved = NPC_MoveToGoal ( qtrue ) ;
navInfo_t info ;
//Get the move info
NAV_GetLastMove ( info ) ;
//FIXME: if we bump into another one of our guys and can't get around him, just stop!
//If we hit our target, then stop and fire!
if ( info . flags & NIF_COLLISION )
{
if ( info . blocker = = NPC - > enemy )
{
GM_HoldPosition ( ) ;
}
}
//If our move failed, then reset
if ( moved = = qfalse )
{ //FIXME: if we're going to a combat point, need to pick a different one
if ( ! Q3_TaskIDPending ( NPC , TID_MOVE_NAV ) )
{ //can't transfer movegoal or stop when a script we're running is waiting to complete
GM_HoldPosition ( ) ;
}
}
return moved ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_BSGM_Patrol
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
void NPC_BSGM_Patrol ( void )
{
if ( NPC_CheckPlayerTeamStealth ( ) )
{
NPC_UpdateAngles ( qtrue , qtrue ) ;
return ;
}
//If we have somewhere to go, then do that
if ( UpdateGoal ( ) )
{
ucmd . buttons | = BUTTON_WALKING ;
NPC_MoveToGoal ( qtrue ) ;
}
NPC_UpdateAngles ( qtrue , qtrue ) ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
GM_CheckMoveState
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
static void GM_CheckMoveState ( void )
{
if ( Q3_TaskIDPending ( NPC , TID_MOVE_NAV ) )
{ //moving toward a goal that a script is waiting on, so don't stop for anything!
move = qtrue ;
}
//See if we're moving towards a goal, not the enemy
if ( ( NPCInfo - > goalEntity ! = NPC - > enemy ) & & ( NPCInfo - > goalEntity ! = NULL ) )
{
//Did we make it?
if ( NAV_HitNavGoal ( NPC - > currentOrigin , NPC - > mins , NPC - > maxs , NPCInfo - > goalEntity - > currentOrigin , 16 ) | |
( ! Q3_TaskIDPending ( NPC , TID_MOVE_NAV ) & & enemyLOS & & enemyDist < = 10000 ) )
{ //either hit our navgoal or our navgoal was not a crucial (scripted) one (maybe a combat point) and we're scouting and found our enemy
NPC_ReachedGoal ( ) ;
//don't attack right away
TIMER_Set ( NPC , " attackDelay " , Q_irand ( 250 , 500 ) ) ; //FIXME: Slant for difficulty levels
return ;
}
}
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
GM_CheckFireState
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
static void GM_CheckFireState ( void )
{
if ( enemyCS )
{ //if have a clear shot, always try
return ;
}
if ( ! VectorCompare ( NPC - > client - > ps . velocity , vec3_origin ) )
{ //if moving at all, don't do this
return ;
}
//See if we should continue to fire on their last position
if ( ! hitAlly & & NPCInfo - > enemyLastSeenTime > 0 )
{
if ( level . time - NPCInfo - > enemyLastSeenTime < 10000 )
{
if ( ! Q_irand ( 0 , 10 ) )
{
//Fire on the last known position
vec3_t muzzle , dir , angles ;
qboolean tooClose = qfalse ;
qboolean tooFar = qfalse ;
CalcEntitySpot ( NPC , SPOT_HEAD , muzzle ) ;
if ( VectorCompare ( impactPos , vec3_origin ) )
{ //never checked ShotEntity this frame, so must do a trace...
trace_t tr ;
//vec3_t mins = {-2,-2,-2}, maxs = {2,2,2};
vec3_t forward , end ;
AngleVectors ( NPC - > client - > ps . viewangles , forward , NULL , NULL ) ;
VectorMA ( muzzle , 8192 , forward , end ) ;
gi . trace ( & tr , muzzle , vec3_origin , vec3_origin , end , NPC - > s . number , MASK_SHOT ) ;
VectorCopy ( tr . endpos , impactPos ) ;
}
//see if impact would be too close to me
float distThreshold = 16384 /*128*128*/ ; //default
if ( NPC - > s . weapon = = WP_REPEATER )
{
if ( NPCInfo - > scriptFlags & SCF_ALT_FIRE )
{
distThreshold = 65536 /*256*256*/ ;
}
}
float dist = DistanceSquared ( impactPos , muzzle ) ;
if ( dist < distThreshold )
{ //impact would be too close to me
tooClose = qtrue ;
}
else if ( level . time - NPCInfo - > enemyLastSeenTime > 5000 )
{ //we've haven't seen them in the last 5 seconds
//see if it's too far from where he is
distThreshold = 65536 /*256*256*/ ; //default
if ( NPC - > s . weapon = = WP_REPEATER )
{
if ( NPCInfo - > scriptFlags & SCF_ALT_FIRE )
{
distThreshold = 262144 /*512*512*/ ;
}
}
dist = DistanceSquared ( impactPos , NPCInfo - > enemyLastSeenLocation ) ;
if ( dist > distThreshold )
{ //impact would be too far from enemy
tooFar = qtrue ;
}
}
if ( ! tooClose & & ! tooFar )
{ //okay too shoot at last pos
VectorSubtract ( NPCInfo - > enemyLastSeenLocation , muzzle , dir ) ;
VectorNormalize ( dir ) ;
vectoangles ( dir , angles ) ;
NPCInfo - > desiredYaw = angles [ YAW ] ;
NPCInfo - > desiredPitch = angles [ PITCH ] ;
shoot = qtrue ;
faceEnemy = qfalse ;
return ;
}
}
}
}
}
void NPC_GM_StartLaser ( void )
{
if ( ! NPC - > lockCount )
{ //haven't already started a laser attack
//warm up for the beam attack
NPC_SetAnim ( NPC , SETANIM_BOTH , TORSO_RAISEWEAP2 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
TIMER_Set ( NPC , " beamDelay " , NPC - > client - > ps . torsoAnimTimer ) ;
TIMER_Set ( NPC , " attackDelay " , NPC - > client - > ps . torsoAnimTimer + 3000 ) ;
NPC - > lockCount = 1 ;
//turn on warmup effect
G_PlayEffect ( " galak/beam_warmup " , NPC - > s . number ) ;
G_SoundOnEnt ( NPC , CHAN_AUTO , " sound/weapons/galak/lasercharge.wav " ) ;
}
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_BSGM_Attack
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
void NPC_BSGM_Attack ( void )
{
//Don't do anything if we're hurt
if ( NPC - > painDebounceTime > level . time )
{
NPC_UpdateAngles ( qtrue , qtrue ) ;
return ;
}
//FIXME: if killed enemy, use victory anim
//If we don't have an enemy, just idle
if ( NPC_CheckEnemyExt ( ) = = qfalse | | ! NPC - > enemy )
{
NPC - > enemy = NULL ;
NPC_BSGM_Patrol ( ) ;
return ;
}
enemyLOS = enemyCS = qfalse ;
move = qtrue ;
faceEnemy = qfalse ;
shoot = qfalse ;
hitAlly = qfalse ;
VectorClear ( impactPos ) ;
enemyDist = DistanceSquared ( NPC - > currentOrigin , NPC - > enemy - > currentOrigin ) ;
if ( NPC - > client - > ps . torsoAnim = = BOTH_ATTACK4 | |
NPC - > client - > ps . torsoAnim = = BOTH_ATTACK5 )
{
shoot = qfalse ;
if ( TIMER_Done ( NPC , " smackTime " ) & & ! NPCInfo - > greetingDebounceTime )
{ //time to smack
//recheck enemyDist and InFront
if ( enemyDist < MELEE_DIST_SQUARED & & InFront ( NPC - > enemy - > currentOrigin , NPC - > currentOrigin , NPC - > client - > ps . viewangles , 0.3f ) )
{
vec3_t smackDir ;
VectorSubtract ( NPC - > enemy - > currentOrigin , NPC - > currentOrigin , smackDir ) ;
smackDir [ 2 ] + = 30 ;
VectorNormalize ( smackDir ) ;
//hurt them
G_Sound ( NPC - > enemy , G_SoundIndex ( " sound/weapons/galak/skewerhit.wav " ) ) ;
G_Damage ( NPC - > enemy , NPC , NPC , smackDir , NPC - > currentOrigin , ( g_spskill - > integer + 1 ) * Q_irand ( 5 , 10 ) , DAMAGE_NO_ARMOR | DAMAGE_NO_KNOCKBACK , MOD_CRUSH ) ;
if ( NPC - > client - > ps . torsoAnim = = BOTH_ATTACK4 )
{ //smackdown
int knockAnim = BOTH_KNOCKDOWN1 ;
if ( PM_CrouchAnim ( NPC - > enemy - > client - > ps . legsAnim ) )
{ //knockdown from crouch
knockAnim = BOTH_KNOCKDOWN4 ;
}
//throw them
2013-04-04 18:02:27 +00:00
smackDir [ 2 ] = 1 ;
VectorNormalize ( smackDir ) ;
G_Throw ( NPC - > enemy , smackDir , 50 ) ;
2013-04-04 14:52:42 +00:00
NPC_SetAnim ( NPC - > enemy , SETANIM_BOTH , knockAnim , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
else
{ //uppercut
//throw them
G_Throw ( NPC - > enemy , smackDir , 100 ) ;
//make them backflip
NPC_SetAnim ( NPC - > enemy , SETANIM_BOTH , BOTH_KNOCKDOWN5 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
//done with the damage
NPCInfo - > greetingDebounceTime = 1 ;
}
}
}
else if ( NPC - > lockCount ) //already shooting laser
{ //sometimes use the laser beam attack, but only after he's taken down our generator
shoot = qfalse ;
if ( NPC - > lockCount = = 1 )
{ //charging up
if ( TIMER_Done ( NPC , " beamDelay " ) )
{ //time to start the beam
int laserAnim ;
if ( Q_irand ( 0 , 1 ) )
{
laserAnim = BOTH_ATTACK2 ;
}
else
{
laserAnim = BOTH_ATTACK7 ;
}
NPC_SetAnim ( NPC , SETANIM_BOTH , laserAnim , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
TIMER_Set ( NPC , " attackDelay " , NPC - > client - > ps . torsoAnimTimer + Q_irand ( 1000 , 3000 ) ) ;
//turn on beam effect
NPC - > lockCount = 2 ;
G_PlayEffect ( " galak/trace_beam " , NPC - > s . number ) ;
NPC - > s . loopSound = G_SoundIndex ( " sound/weapons/galak/lasercutting.wav " ) ;
if ( ! NPCInfo - > coverTarg )
{ //for moving looping sound at end of trace
NPCInfo - > coverTarg = G_Spawn ( ) ;
if ( NPCInfo - > coverTarg )
{
G_SetOrigin ( NPCInfo - > coverTarg , NPC - > client - > renderInfo . muzzlePoint ) ;
NPCInfo - > coverTarg - > svFlags | = SVF_BROADCAST ;
NPCInfo - > coverTarg - > s . loopSound = G_SoundIndex ( " sound/weapons/galak/lasercutting.wav " ) ;
}
}
}
}
else
{ //in the actual attack now
if ( ! NPC - > client - > ps . torsoAnimTimer )
{ //attack done!
NPC - > lockCount = 0 ;
G_FreeEntity ( NPCInfo - > coverTarg ) ;
NPC - > s . loopSound = 0 ;
NPC_SetAnim ( NPC , SETANIM_BOTH , TORSO_DROPWEAP2 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
TIMER_Set ( NPC , " attackDelay " , NPC - > client - > ps . torsoAnimTimer ) ;
}
else
{ //attack still going
//do the trace and damage
trace_t trace ;
vec3_t end , mins = { - 3 , - 3 , - 3 } , maxs = { 3 , 3 , 3 } ;
VectorMA ( NPC - > client - > renderInfo . muzzlePoint , 1024 , NPC - > client - > renderInfo . muzzleDir , end ) ;
gi . trace ( & trace , NPC - > client - > renderInfo . muzzlePoint , mins , maxs , end , NPC - > s . number , MASK_SHOT ) ;
if ( trace . allsolid | | trace . startsolid )
{ //oops, in a wall
if ( NPCInfo - > coverTarg )
{
G_SetOrigin ( NPCInfo - > coverTarg , NPC - > client - > renderInfo . muzzlePoint ) ;
}
}
else
{ //clear
if ( trace . fraction < 1.0f )
{ //hit something
gentity_t * traceEnt = & g_entities [ trace . entityNum ] ;
if ( traceEnt & & traceEnt - > takedamage )
{ //damage it
G_SoundAtSpot ( trace . endpos , G_SoundIndex ( " sound/weapons/galak/laserdamage.wav " ) ) ;
G_Damage ( traceEnt , NPC , NPC , NPC - > client - > renderInfo . muzzleDir , trace . endpos , 10 , 0 , MOD_ENERGY ) ;
}
}
if ( NPCInfo - > coverTarg )
{
G_SetOrigin ( NPCInfo - > coverTarg , trace . endpos ) ;
}
if ( ! Q_irand ( 0 , 5 ) )
{
G_SoundAtSpot ( trace . endpos , G_SoundIndex ( " sound/weapons/galak/laserdamage.wav " ) ) ;
}
}
}
}
}
else
{ //Okay, we're not in a special attack, see if we should switch weapons or start a special attack
/*
if ( NPC - > s . weapon = = WP_REPEATER
& & ! ( NPCInfo - > scriptFlags & SCF_ALT_FIRE ) //using rapid-fire
& & NPC - > enemy - > s . weapon = = WP_SABER //enemy using saber
& & NPC - > client & & ( NPC - > client - > ps . saberEventFlags & SEF_DEFLECTED )
& & ! Q_irand ( 0 , 50 ) )
{ //he's deflecting my shots, switch to the laser or the lob fire for a while
TIMER_Set ( NPC , " noRapid " , Q_irand ( 2000 , 6000 ) ) ;
NPCInfo - > scriptFlags | = SCF_ALT_FIRE ;
NPC - > alt_fire = qtrue ;
if ( NPC - > locationDamage [ HL_GENERIC1 ] > GENERATOR_HEALTH & & ( Q_irand ( 0 , 1 ) | | enemyDist < MAX_LOB_DIST_SQUARED ) )
{ //shield down, use laser
NPC_GM_StartLaser ( ) ;
}
}
else */
if ( ! NPC - > client - > ps . powerups [ PW_GALAK_SHIELD ] & & enemyDist < MELEE_DIST_SQUARED & & InFront ( NPC - > enemy - > currentOrigin , NPC - > currentOrigin , NPC - > client - > ps . viewangles , 0.3f ) ) //within 80 and in front
{ //our shield is down, and enemy within 80, if very close, use melee attack to slap away
if ( TIMER_Done ( NPC , " attackDelay " ) )
{
//animate me
int swingAnim ;
if ( NPC - > locationDamage [ HL_GENERIC1 ] > GENERATOR_HEALTH )
{ //generator down, use random melee
swingAnim = Q_irand ( BOTH_ATTACK4 , BOTH_ATTACK5 ) ; //smackdown or uppercut
}
else
{ //always knock-away
swingAnim = BOTH_ATTACK5 ; //uppercut
}
//FIXME: swing sound
NPC_SetAnim ( NPC , SETANIM_BOTH , swingAnim , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
TIMER_Set ( NPC , " attackDelay " , NPC - > client - > ps . torsoAnimTimer + Q_irand ( 1000 , 3000 ) ) ;
//delay the hurt until the proper point in the anim
TIMER_Set ( NPC , " smackTime " , 600 ) ;
NPCInfo - > greetingDebounceTime = 0 ;
//FIXME: say something?
}
}
else if ( ! NPC - > lockCount & & NPC - > locationDamage [ HL_GENERIC1 ] > GENERATOR_HEALTH
& & TIMER_Done ( NPC , " attackDelay " )
& & InFront ( NPC - > enemy - > currentOrigin , NPC - > currentOrigin , NPC - > client - > ps . viewangles , 0.3f )
& & ( ( ! Q_irand ( 0 , 10 * ( 2 - g_spskill - > integer ) ) & & enemyDist > MIN_LOB_DIST_SQUARED & & enemyDist < MAX_LOB_DIST_SQUARED )
| | ( ! TIMER_Done ( NPC , " noLob " ) & & ! TIMER_Done ( NPC , " noRapid " ) ) ) )
{ //sometimes use the laser beam attack, but only after he's taken down our generator
shoot = qfalse ;
NPC_GM_StartLaser ( ) ;
}
else if ( enemyDist < MIN_LOB_DIST_SQUARED & & TIMER_Done ( NPC , " noRapid " ) ) //256
{ //enemy within 256
if ( ( NPC - > client - > ps . weapon = = WP_REPEATER ) & & ( NPCInfo - > scriptFlags & SCF_ALT_FIRE ) )
{ //shooting an explosive, but enemy too close, switch to primary fire
NPCInfo - > scriptFlags & = ~ SCF_ALT_FIRE ;
NPC - > alt_fire = qfalse ;
//FIXME: use weap raise & lower anims
NPC_ChangeWeapon ( WP_REPEATER ) ;
}
}
else if ( enemyDist > MAX_LOB_DIST_SQUARED & & TIMER_Done ( NPC , " noLob " ) ) //448
{ //enemy more than 448 away and we are ready to try lob fire again
if ( ( NPC - > client - > ps . weapon = = WP_REPEATER ) & & ! ( NPCInfo - > scriptFlags & SCF_ALT_FIRE ) )
{ //enemy far enough away to use lobby explosives
NPCInfo - > scriptFlags | = SCF_ALT_FIRE ;
NPC - > alt_fire = qtrue ;
//FIXME: use weap raise & lower anims
NPC_ChangeWeapon ( WP_REPEATER ) ;
}
}
}
//can we see our target?
if ( NPC_ClearLOS ( NPC - > enemy ) )
{
NPCInfo - > enemyLastSeenTime = level . time ;
enemyLOS = qtrue ;
if ( NPC - > client - > ps . weapon = = WP_NONE )
{
enemyCS = qfalse ; //not true, but should stop us from firing
NPC_AimAdjust ( - 1 ) ; //adjust aim worse longer we have no weapon
}
else
{ //can we shoot our target?
if ( ( ( NPC - > client - > ps . weapon = = WP_REPEATER & & ( NPCInfo - > scriptFlags & SCF_ALT_FIRE ) ) ) & & enemyDist < MIN_LOB_DIST_SQUARED ) //256
{
enemyCS = qfalse ; //not true, but should stop us from firing
hitAlly = qtrue ; //us!
//FIXME: if too close, run away!
}
else
{
int hit = NPC_ShotEntity ( NPC - > enemy , impactPos ) ;
2013-04-04 18:02:27 +00:00
gentity_t * hitEnt = & g_entities [ hit ] ;
if ( hit = = NPC - > enemy - > s . number
| | ( hitEnt & & hitEnt - > client & & hitEnt - > client - > playerTeam = = NPC - > client - > enemyTeam )
| | ( hitEnt & & hitEnt - > takedamage ) )
{ //can hit enemy or will hit glass or other breakable, so shoot anyway
2013-04-04 14:52:42 +00:00
enemyCS = qtrue ;
NPC_AimAdjust ( 2 ) ; //adjust aim better longer we have clear shot at enemy
VectorCopy ( NPC - > enemy - > currentOrigin , NPCInfo - > enemyLastSeenLocation ) ;
}
else
{ //Hmm, have to get around this bastard
NPC_AimAdjust ( 1 ) ; //adjust aim better longer we can see enemy
if ( hitEnt & & hitEnt - > client & & hitEnt - > client - > playerTeam = = NPC - > client - > playerTeam )
{ //would hit an ally, don't fire!!!
hitAlly = qtrue ;
}
else
{ //Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire
}
}
}
}
}
else if ( gi . inPVS ( NPC - > enemy - > currentOrigin , NPC - > currentOrigin ) )
{
2013-04-04 18:02:27 +00:00
int hit = NPC_ShotEntity ( NPC - > enemy , impactPos ) ;
gentity_t * hitEnt = & g_entities [ hit ] ;
if ( hit = = NPC - > enemy - > s . number
| | ( hitEnt & & hitEnt - > client & & hitEnt - > client - > playerTeam = = NPC - > client - > enemyTeam )
| | ( hitEnt & & hitEnt - > takedamage ) )
{ //can hit enemy or will hit glass or other breakable, so shoot anyway
enemyCS = qtrue ;
}
else
{
NPCInfo - > enemyLastSeenTime = level . time ;
faceEnemy = qtrue ;
NPC_AimAdjust ( - 1 ) ; //adjust aim worse longer we cannot see enemy
}
2013-04-04 14:52:42 +00:00
}
if ( enemyLOS )
{
faceEnemy = qtrue ;
}
else
{
if ( ! NPCInfo - > goalEntity )
{
NPCInfo - > goalEntity = NPC - > enemy ;
}
if ( NPCInfo - > goalEntity = = NPC - > enemy )
{ //for now, always chase the enemy
move = qtrue ;
}
}
if ( enemyCS )
{
shoot = qtrue ;
}
else
{
if ( ! NPCInfo - > goalEntity )
{
NPCInfo - > goalEntity = NPC - > enemy ;
}
if ( NPCInfo - > goalEntity = = NPC - > enemy )
{ //for now, always chase the enemy
move = qtrue ;
}
}
//Check for movement to take care of
GM_CheckMoveState ( ) ;
//See if we should override shooting decision with any special considerations
GM_CheckFireState ( ) ;
if ( NPC - > client - > ps . weapon = = WP_REPEATER & & ( NPCInfo - > scriptFlags & SCF_ALT_FIRE ) & & shoot & & TIMER_Done ( NPC , " attackDelay " ) )
{
vec3_t muzzle ;
vec3_t angles ;
vec3_t target ;
vec3_t velocity = { 0 , 0 , 0 } ;
vec3_t mins = { - REPEATER_ALT_SIZE , - REPEATER_ALT_SIZE , - REPEATER_ALT_SIZE } , maxs = { REPEATER_ALT_SIZE , REPEATER_ALT_SIZE , REPEATER_ALT_SIZE } ;
CalcEntitySpot ( NPC , SPOT_WEAPON , muzzle ) ;
VectorCopy ( NPC - > enemy - > currentOrigin , target ) ;
2013-04-04 18:02:27 +00:00
target [ 0 ] + = Q_flrand ( - 5 , 5 ) + ( crandom ( ) * ( 6 - NPCInfo - > currentAim ) * 2 ) ;
target [ 1 ] + = Q_flrand ( - 5 , 5 ) + ( crandom ( ) * ( 6 - NPCInfo - > currentAim ) * 2 ) ;
target [ 2 ] + = Q_flrand ( - 5 , 5 ) + ( crandom ( ) * ( 6 - NPCInfo - > currentAim ) * 2 ) ;
2013-04-04 14:52:42 +00:00
//Find the desired angles
qboolean clearshot = WP_LobFire ( NPC , muzzle , target , mins , maxs , MASK_SHOT | CONTENTS_LIGHTSABER ,
velocity , qtrue , NPC - > s . number , NPC - > enemy - > s . number ,
300 , 1100 , 1500 , qtrue ) ;
if ( VectorCompare ( vec3_origin , velocity ) | | ( ! clearshot & & enemyLOS & & enemyCS ) )
{ //no clear lob shot and no lob shot that will hit something breakable
if ( enemyLOS & & enemyCS & & TIMER_Done ( NPC , " noRapid " ) )
{ //have a clear straight shot, so switch to primary
NPCInfo - > scriptFlags & = ~ SCF_ALT_FIRE ;
NPC - > alt_fire = qfalse ;
NPC_ChangeWeapon ( WP_REPEATER ) ;
//keep this weap for a bit
TIMER_Set ( NPC , " noLob " , Q_irand ( 500 , 1000 ) ) ;
}
else
{
shoot = qfalse ;
}
}
else
{
vectoangles ( velocity , angles ) ;
NPCInfo - > desiredYaw = AngleNormalize360 ( angles [ YAW ] ) ;
NPCInfo - > desiredPitch = AngleNormalize360 ( angles [ PITCH ] ) ;
VectorCopy ( velocity , NPC - > client - > hiddenDir ) ;
NPC - > client - > hiddenDist = VectorNormalize ( NPC - > client - > hiddenDir ) ;
}
}
else if ( faceEnemy )
{ //face the enemy
NPC_FaceEnemy ( qtrue ) ;
}
if ( ! TIMER_Done ( NPC , " standTime " ) )
{
move = qfalse ;
}
if ( ! ( NPCInfo - > scriptFlags & SCF_CHASE_ENEMIES ) )
{ //not supposed to chase my enemies
if ( NPCInfo - > goalEntity = = NPC - > enemy )
{ //goal is my entity, so don't move
move = qfalse ;
}
}
if ( move & & ! NPC - > lockCount )
{ //move toward goal
if ( NPCInfo - > goalEntity )
{
move = GM_Move ( ) ;
}
else
{
move = qfalse ;
}
}
if ( ! TIMER_Done ( NPC , " flee " ) )
{ //running away
faceEnemy = qfalse ;
}
//FIXME: check scf_face_move_dir here?
if ( ! faceEnemy )
{ //we want to face in the dir we're running
2013-04-04 18:02:27 +00:00
if ( ! move )
{ //if we haven't moved, we should look in the direction we last looked?
VectorCopy ( NPC - > client - > ps . viewangles , NPCInfo - > lastPathAngles ) ;
}
2013-04-04 14:52:42 +00:00
if ( move )
{ //don't run away and shoot
2013-04-04 18:02:27 +00:00
NPCInfo - > desiredYaw = NPCInfo - > lastPathAngles [ YAW ] ;
NPCInfo - > desiredPitch = 0 ;
2013-04-04 14:52:42 +00:00
shoot = qfalse ;
}
}
NPC_UpdateAngles ( qtrue , qtrue ) ;
if ( NPCInfo - > scriptFlags & SCF_DONT_FIRE )
{
shoot = qfalse ;
}
if ( NPC - > enemy & & NPC - > enemy - > enemy )
{
if ( NPC - > enemy - > s . weapon = = WP_SABER & & NPC - > enemy - > enemy - > s . weapon = = WP_SABER )
{ //don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH)
shoot = qfalse ;
}
}
//FIXME: don't shoot right away!
if ( shoot )
{ //try to shoot if it's time
if ( TIMER_Done ( NPC , " attackDelay " ) )
{
if ( ! ( NPCInfo - > scriptFlags & SCF_FIRE_WEAPON ) ) // we've already fired, no need to do it again here
{
WeaponThink ( qtrue ) ;
}
}
}
//also:
if ( NPCInfo - > touchedByPlayer ! = NULL & & NPCInfo - > touchedByPlayer = = NPC - > enemy & & NPC - > client - > ps . powerups [ PW_GALAK_SHIELD ] > 0 )
{ //zap him!
//animate me
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_ATTACK6 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
TIMER_Set ( NPC , " attackDelay " , NPC - > client - > ps . torsoAnimTimer ) ;
TIMER_Set ( NPC , " standTime " , NPC - > client - > ps . legsAnimTimer ) ;
//FIXME: debounce this?
NPCInfo - > touchedByPlayer = NULL ;
//FIXME: some shield effect?
NPC - > client - > ps . powerups [ PW_BATTLESUIT ] = level . time + ARMOR_EFFECT_TIME ;
vec3_t smackDir ;
VectorSubtract ( NPC - > enemy - > currentOrigin , NPC - > currentOrigin , smackDir ) ;
smackDir [ 2 ] + = 30 ;
VectorNormalize ( smackDir ) ;
G_Damage ( NPC - > enemy , NPC , NPC , smackDir , NPC - > currentOrigin , ( g_spskill - > integer + 1 ) * Q_irand ( 5 , 10 ) , DAMAGE_NO_KNOCKBACK , MOD_ELECTROCUTE ) ;
//throw them
G_Throw ( NPC - > enemy , smackDir , 100 ) ;
NPC - > enemy - > s . powerups | = ( 1 < < PW_SHOCKED ) ;
NPC - > enemy - > client - > ps . powerups [ PW_SHOCKED ] = level . time + 1000 ;
//stop any attacks
ucmd . buttons = 0 ;
}
}
void NPC_BSGM_Default ( void )
{
if ( NPCInfo - > scriptFlags & SCF_FIRE_WEAPON )
{
WeaponThink ( qtrue ) ;
}
if ( ! NPC - > enemy )
{ //don't have an enemy, look for one
NPC_BSGM_Patrol ( ) ;
}
else //if ( NPC->enemy )
{ //have an enemy
if ( NPC - > client - > ps . stats [ STAT_ARMOR ] < = 0 )
{ //armor gone
if ( ! NPCInfo - > investigateDebounceTime )
{ //start regenerating the armor
2013-04-04 18:02:27 +00:00
gi . G2API_SetSurfaceOnOff ( & NPC - > ghoul2 [ NPC - > playerModel ] , " torso_shield_off " , TURN_OFF ) ;
2013-04-04 14:52:42 +00:00
NPC - > flags & = ~ FL_SHIELDED ; //no more reflections
VectorSet ( NPC - > mins , - 20 , - 20 , - 24 ) ;
VectorSet ( NPC - > maxs , 20 , 20 , 64 ) ;
NPC - > client - > crouchheight = NPC - > client - > standheight = 64 ;
if ( NPC - > locationDamage [ HL_GENERIC1 ] < GENERATOR_HEALTH )
{ //still have the generator bolt-on
if ( NPCInfo - > investigateCount < 12 )
{
NPCInfo - > investigateCount + + ;
}
NPCInfo - > investigateDebounceTime = level . time + ( NPCInfo - > investigateCount * 5000 ) ;
}
}
else if ( NPCInfo - > investigateDebounceTime < level . time )
{ //armor regenerated, turn shield back on
//do a trace and make sure we can turn this back on?
trace_t tr ;
gi . trace ( & tr , NPC - > currentOrigin , shieldMins , shieldMaxs , NPC - > currentOrigin , NPC - > s . number , NPC - > clipmask ) ;
if ( ! tr . startsolid )
{
VectorCopy ( shieldMins , NPC - > mins ) ;
VectorCopy ( shieldMaxs , NPC - > maxs ) ;
NPC - > client - > crouchheight = NPC - > client - > standheight = shieldMaxs [ 2 ] ;
NPC - > client - > ps . stats [ STAT_ARMOR ] = GALAK_SHIELD_HEALTH ;
NPCInfo - > investigateDebounceTime = 0 ;
NPC - > flags | = FL_SHIELDED ; //reflect normal shots
NPC - > fx_time = level . time ;
2013-04-04 18:02:27 +00:00
gi . G2API_SetSurfaceOnOff ( & NPC - > ghoul2 [ NPC - > playerModel ] , " torso_shield_off " , TURN_ON ) ;
2013-04-04 14:52:42 +00:00
}
}
}
if ( NPC - > client - > ps . stats [ STAT_ARMOR ] > 0 )
{ //armor present
NPC - > client - > ps . powerups [ PW_GALAK_SHIELD ] = Q3_INFINITE ; //temp, for effect
}
NPC_BSGM_Attack ( ) ;
}
}