2013-04-19 02:52:48 +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"
// These define the working combat range for these suckers
# define MIN_DISTANCE 48
# define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE )
# define MAX_DISTANCE 1024
# define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE )
# define LSTATE_CLEAR 0
# define LSTATE_WAITING 1
float enemyDist = 0 ;
extern qboolean NAV_CheckAhead ( gentity_t * self , vec3_t end , trace_t & trace , int clipmask ) ;
extern int PM_AnimLength ( int index , animNumber_t anim ) ;
extern cvar_t * g_dismemberment ;
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_Wampa_Precache
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
void NPC_Wampa_Precache ( void )
{
/*
int i ;
for ( i = 1 ; i < 4 ; i + + )
{
G_SoundIndex ( va ( " sound/chars/wampa/growl%d.wav " , i ) ) ;
}
for ( i = 1 ; i < 3 ; i + + )
{
G_SoundIndex ( va ( " sound/chars/wampa/snort%d.wav " , i ) ) ;
}
*/
G_SoundIndex ( " sound/chars/rancor/swipehit.wav " ) ;
//G_SoundIndex( "sound/chars/wampa/chomp.wav" );
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
Wampa_Idle
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
void Wampa_Idle ( void )
{
NPCInfo - > localState = LSTATE_CLEAR ;
//If we have somewhere to go, then do that
if ( UpdateGoal ( ) )
{
ucmd . buttons & = ~ BUTTON_WALKING ;
NPC_MoveToGoal ( qtrue ) ;
}
}
qboolean Wampa_CheckRoar ( gentity_t * self )
{
if ( self - > wait < level . time )
{
self - > wait = level . time + Q_irand ( 5000 , 20000 ) ;
NPC_SetAnim ( self , SETANIM_BOTH , Q_irand ( BOTH_GESTURE1 , BOTH_GESTURE2 ) , ( SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ) ;
TIMER_Set ( self , " rageTime " , self - > client - > ps . legsAnimTimer ) ;
return qtrue ;
}
return qfalse ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
Wampa_Patrol
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
void Wampa_Patrol ( void )
{
NPCInfo - > localState = LSTATE_CLEAR ;
//If we have somewhere to go, then do that
if ( UpdateGoal ( ) )
{
ucmd . buttons | = BUTTON_WALKING ;
NPC_MoveToGoal ( qtrue ) ;
}
if ( NPC_CheckEnemyExt ( qtrue ) = = qfalse )
{
Wampa_Idle ( ) ;
return ;
}
Wampa_CheckRoar ( NPC ) ;
TIMER_Set ( NPC , " lookForNewEnemy " , Q_irand ( 5000 , 15000 ) ) ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
Wampa_Move
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
void Wampa_Move ( qboolean visible )
{
if ( NPCInfo - > localState ! = LSTATE_WAITING )
{
NPCInfo - > goalEntity = NPC - > enemy ;
trace_t trace ;
if ( ! NAV_CheckAhead ( NPC , NPCInfo - > goalEntity - > currentOrigin , trace , ( NPC - > clipmask | CONTENTS_BOTCLIP ) ) )
{
if ( ! NPC_MoveToGoal ( qfalse ) )
{
STEER : : Activate ( NPC ) ;
STEER : : Seek ( NPC , NPCInfo - > goalEntity - > currentOrigin ) ;
STEER : : AvoidCollisions ( NPC ) ;
STEER : : DeActivate ( NPC , & ucmd ) ;
}
}
NPCInfo - > goalRadius = MIN_DISTANCE ; //MAX_DISTANCE; // just get us within combat range
if ( NPC - > enemy )
{ //pick correct movement speed and anim
//run by default
ucmd . buttons & = ~ BUTTON_WALKING ;
if ( ! TIMER_Done ( NPC , " runfar " )
| | ! TIMER_Done ( NPC , " runclose " ) )
{ //keep running with this anim & speed for a bit
}
else if ( ! TIMER_Done ( NPC , " walk " ) )
{ //keep walking for a bit
ucmd . buttons | = BUTTON_WALKING ;
}
else if ( visible & & enemyDist > 350 & & NPCInfo - > stats . runSpeed = = 200 ) //180 )
{ //fast run, all fours
//BOTH_RUN1
NPCInfo - > stats . runSpeed = 300 ;
TIMER_Set ( NPC , " runfar " , Q_irand ( 4000 , 8000 ) ) ;
if ( NPC - > client - > ps . legsAnim = = BOTH_RUN2 )
{
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_RUN2TORUN1 , SETANIM_FLAG_HOLD ) ;
}
}
else if ( enemyDist > 200 & & NPCInfo - > stats . runSpeed = = 300 )
{ //slow run, upright
//BOTH_RUN2
NPCInfo - > stats . runSpeed = 200 ; //180;
TIMER_Set ( NPC , " runclose " , Q_irand ( 5000 , 10000 ) ) ;
if ( NPC - > client - > ps . legsAnim = = BOTH_RUN1 )
{
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_RUN1TORUN2 , SETANIM_FLAG_HOLD ) ;
}
}
else if ( enemyDist < 100 )
{ //walk
NPCInfo - > stats . runSpeed = 200 ; //180;
ucmd . buttons | = BUTTON_WALKING ;
TIMER_Set ( NPC , " walk " , Q_irand ( 6000 , 12000 ) ) ;
}
}
}
}
//---------------------------------------------------------
extern void G_Knockdown ( gentity_t * self , gentity_t * attacker , const vec3_t pushDir , float strength , qboolean breakSaberLock ) ;
extern qboolean G_DoDismemberment ( gentity_t * self , vec3_t point , int mod , int damage , int hitLoc , qboolean force = qfalse ) ;
extern int NPC_GetEntsNearBolt ( gentity_t * * radiusEnts , float radius , int boltIndex , vec3_t boltOrg ) ;
void Wampa_Slash ( int boltIndex , qboolean backhand )
{
gentity_t * radiusEnts [ 128 ] ;
int numEnts ;
const float radius = 88 ;
const float radiusSquared = ( radius * radius ) ;
int i ;
vec3_t boltOrg ;
int damage = ( backhand ) ? Q_irand ( 10 , 15 ) : Q_irand ( 20 , 30 ) ;
numEnts = NPC_GetEntsNearBolt ( radiusEnts , radius , boltIndex , boltOrg ) ;
for ( i = 0 ; i < numEnts ; i + + )
{
if ( ! radiusEnts [ i ] - > inuse )
{
continue ;
}
if ( radiusEnts [ i ] = = NPC )
{ //Skip the wampa ent
continue ;
}
if ( radiusEnts [ i ] - > client = = NULL )
{ //must be a client
continue ;
}
if ( DistanceSquared ( radiusEnts [ i ] - > currentOrigin , boltOrg ) < = radiusSquared )
{
//smack
G_Damage ( radiusEnts [ i ] , NPC , NPC , vec3_origin , radiusEnts [ i ] - > currentOrigin , damage , ( ( backhand ) ? 0 : DAMAGE_NO_KNOCKBACK ) , MOD_MELEE ) ;
if ( backhand )
{
//actually push the enemy
vec3_t pushDir ;
vec3_t angs ;
VectorCopy ( NPC - > client - > ps . viewangles , angs ) ;
angs [ YAW ] + = Q_flrand ( 25 , 50 ) ;
angs [ PITCH ] = Q_flrand ( - 25 , - 15 ) ;
AngleVectors ( angs , pushDir , NULL , NULL ) ;
if ( radiusEnts [ i ] - > client - > NPC_class ! = CLASS_WAMPA
& & radiusEnts [ i ] - > client - > NPC_class ! = CLASS_RANCOR
& & radiusEnts [ i ] - > client - > NPC_class ! = CLASS_ATST
& & ! ( radiusEnts [ i ] - > flags & FL_NO_KNOCKBACK ) )
{
G_Throw ( radiusEnts [ i ] , pushDir , 65 ) ;
if ( radiusEnts [ i ] - > health > 0 & & Q_irand ( 0 , 1 ) )
{ //do pain on enemy
G_Knockdown ( radiusEnts [ i ] , NPC , pushDir , 300 , qtrue ) ;
}
}
}
else if ( radiusEnts [ i ] - > health < = 0 & & radiusEnts [ i ] - > client )
{ //killed them, chance of dismembering
if ( ! Q_irand ( 0 , 1 ) )
{ //bite something off
int hitLoc = HL_WAIST ;
if ( g_dismemberment - > integer < 4 )
{
hitLoc = Q_irand ( HL_BACK_RT , HL_HAND_LT ) ;
}
else
{
hitLoc = Q_irand ( HL_WAIST , HL_HEAD ) ;
}
if ( hitLoc = = HL_HEAD )
{
NPC_SetAnim ( radiusEnts [ i ] , SETANIM_BOTH , BOTH_DEATH17 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
else if ( hitLoc = = HL_WAIST )
{
NPC_SetAnim ( radiusEnts [ i ] , SETANIM_BOTH , BOTH_DEATHBACKWARD2 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
radiusEnts [ i ] - > client - > dismembered = false ;
//FIXME: the limb should just disappear, cuz I ate it
G_DoDismemberment ( radiusEnts [ i ] , radiusEnts [ i ] - > currentOrigin , MOD_SABER , 1000 , hitLoc , qtrue ) ;
}
}
else if ( ! Q_irand ( 0 , 3 ) & & radiusEnts [ i ] - > health > 0 )
{ //one out of every 4 normal hits does a knockdown, too
vec3_t pushDir ;
vec3_t angs ;
VectorCopy ( NPC - > client - > ps . viewangles , angs ) ;
angs [ YAW ] + = Q_flrand ( 25 , 50 ) ;
angs [ PITCH ] = Q_flrand ( - 25 , - 15 ) ;
AngleVectors ( angs , pushDir , NULL , NULL ) ;
G_Knockdown ( radiusEnts [ i ] , NPC , pushDir , 35 , qtrue ) ;
}
G_Sound ( radiusEnts [ i ] , G_SoundIndex ( " sound/chars/rancor/swipehit.wav " ) ) ;
}
}
}
//------------------------------
void Wampa_Attack ( float distance , qboolean doCharge )
{
if ( ! TIMER_Exists ( NPC , " attacking " ) )
{
if ( ! Q_irand ( 0 , 3 ) & & ! doCharge )
{ //double slash
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_ATTACK1 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
TIMER_Set ( NPC , " attack_dmg " , 750 ) ;
}
else if ( doCharge | | ( distance > 270 & & distance < 430 & & ! Q_irand ( 0 , 1 ) ) )
{ //leap
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_ATTACK2 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
TIMER_Set ( NPC , " attack_dmg " , 500 ) ;
vec3_t fwd , yawAng = { 0 , NPC - > client - > ps . viewangles [ YAW ] , 0 } ;
AngleVectors ( yawAng , fwd , NULL , NULL ) ;
VectorScale ( fwd , distance * 1.5f , NPC - > client - > ps . velocity ) ;
NPC - > client - > ps . velocity [ 2 ] = 150 ;
NPC - > client - > ps . groundEntityNum = ENTITYNUM_NONE ;
}
else if ( distance < 100 ) //&& !Q_irand( 0, 4 ) )
{ //grab
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_HOLD_START , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
NPC - > client - > ps . legsAnimTimer + = 200 ;
TIMER_Set ( NPC , " attack_dmg " , 250 ) ;
}
else
{ //backhand
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_ATTACK3 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
TIMER_Set ( NPC , " attack_dmg " , 250 ) ;
}
TIMER_Set ( NPC , " attacking " , NPC - > client - > ps . legsAnimTimer + random ( ) * 200 ) ;
//allow us to re-evaluate our running speed/anim
TIMER_Set ( NPC , " runfar " , - 1 ) ;
TIMER_Set ( NPC , " runclose " , - 1 ) ;
TIMER_Set ( NPC , " walk " , - 1 ) ;
}
// Need to do delayed damage since the attack animations encapsulate multiple mini-attacks
if ( TIMER_Done2 ( NPC , " attack_dmg " , qtrue ) )
{
switch ( NPC - > client - > ps . legsAnim )
{
case BOTH_ATTACK1 :
Wampa_Slash ( NPC - > handRBolt , qfalse ) ;
//do second hit
TIMER_Set ( NPC , " attack_dmg2 " , 100 ) ;
break ;
case BOTH_ATTACK2 :
Wampa_Slash ( NPC - > handRBolt , qfalse ) ;
TIMER_Set ( NPC , " attack_dmg2 " , 100 ) ;
break ;
case BOTH_ATTACK3 :
Wampa_Slash ( NPC - > handLBolt , qtrue ) ;
break ;
}
}
else if ( TIMER_Done2 ( NPC , " attack_dmg2 " , qtrue ) )
{
switch ( NPC - > client - > ps . legsAnim )
{
case BOTH_ATTACK1 :
Wampa_Slash ( NPC - > handLBolt , qfalse ) ;
break ;
case BOTH_ATTACK2 :
Wampa_Slash ( NPC - > handLBolt , qfalse ) ;
break ;
}
}
// Just using this to remove the attacking flag at the right time
TIMER_Done2 ( NPC , " attacking " , qtrue ) ;
if ( NPC - > client - > ps . legsAnim = = BOTH_ATTACK1 & & distance > ( NPC - > maxs [ 0 ] + MIN_DISTANCE ) )
{ //okay to keep moving
ucmd . buttons | = BUTTON_WALKING ;
Wampa_Move ( 1 ) ;
}
}
//----------------------------------
void Wampa_Combat ( void )
{
// If we cannot see our target or we have somewhere to go, then do that
if ( ! NPC_ClearLOS ( NPC - > enemy ) )
{
if ( ! Q_irand ( 0 , 10 ) )
{
if ( Wampa_CheckRoar ( NPC ) )
{
return ;
}
}
NPCInfo - > combatMove = qtrue ;
NPCInfo - > goalEntity = NPC - > enemy ;
NPCInfo - > goalRadius = MIN_DISTANCE ; //MAX_DISTANCE; // just get us within combat range
Wampa_Move ( 0 ) ;
return ;
}
/*
else if ( UpdateGoal ( ) )
{
NPCInfo - > combatMove = qtrue ;
NPCInfo - > goalEntity = NPC - > enemy ;
NPCInfo - > goalRadius = MIN_DISTANCE ; //MAX_DISTANCE; // just get us within combat range
Wampa_Move ( 1 ) ;
return ;
} */
// Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb
//FIXME: always seems to face off to the left or right?!!!!
NPC_FaceEnemy ( qtrue ) ;
float distance = enemyDist = Distance ( NPC - > currentOrigin , NPC - > enemy - > currentOrigin ) ;
qboolean advance = ( qboolean ) ( distance > ( NPC - > maxs [ 0 ] + MIN_DISTANCE ) ? qtrue : qfalse ) ;
qboolean doCharge = qfalse ;
if ( advance )
{ //have to get closer
vec3_t yawOnlyAngles = { 0 , NPC - > currentAngles [ YAW ] , 0 } ;
if ( NPC - > enemy - > health > 0 //enemy still alive
& & fabs ( distance - 350 ) < = 80 //enemy anywhere from 270 to 430 away
& & InFOV ( NPC - > enemy - > currentOrigin , NPC - > currentOrigin , yawOnlyAngles , 20 , 20 ) ) //enemy generally in front
{ //10% chance of doing charge anim
if ( ! Q_irand ( 0 , 6 ) )
{ //go for the charge
doCharge = qtrue ;
advance = qfalse ;
}
}
}
if ( ( advance | | NPCInfo - > localState = = LSTATE_WAITING ) & & TIMER_Done ( NPC , " attacking " ) ) // waiting monsters can't attack
{
if ( TIMER_Done2 ( NPC , " takingPain " , qtrue ) )
{
NPCInfo - > localState = LSTATE_CLEAR ;
}
else
{
Wampa_Move ( 1 ) ;
}
}
else
{
if ( ! Q_irand ( 0 , 15 ) )
{ //FIXME: only do this if we just damaged them or vice-versa?
if ( Wampa_CheckRoar ( NPC ) )
{
return ;
}
}
Wampa_Attack ( distance , doCharge ) ;
}
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_Wampa_Pain
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
void NPC_Wampa_Pain ( gentity_t * self , gentity_t * inflictor , gentity_t * other , const vec3_t point , int damage , int mod , int hitLoc )
{
qboolean hitByWampa = qfalse ;
if ( self - > count )
{ //FIXME: need pain anim
NPC_SetAnim ( self , SETANIM_BOTH , BOTH_STAND2TO1 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
TIMER_Set ( self , " takingPain " , self - > client - > ps . legsAnimTimer ) ;
TIMER_Set ( self , " attacking " , - level . time ) ;
return ;
}
if ( other & & other - > client & & other - > client - > NPC_class = = CLASS_WAMPA )
{
hitByWampa = qtrue ;
}
if ( other
& & other - > inuse
& & other ! = self - > enemy
& & ! ( other - > flags & FL_NOTARGET ) )
{
if ( ( ! other - > s . number & & ! Q_irand ( 0 , 3 ) )
| | ! self - > enemy
| | self - > enemy - > health = = 0
| | ( self - > enemy - > client & & self - > enemy - > client - > NPC_class = = CLASS_WAMPA )
| | ( ! Q_irand ( 0 , 4 ) & & DistanceSquared ( other - > currentOrigin , self - > currentOrigin ) < DistanceSquared ( self - > enemy - > currentOrigin , self - > currentOrigin ) ) )
{ //if my enemy is dead (or attacked by player) and I'm not still holding/eating someone, turn on the attacker
//FIXME: if can't nav to my enemy, take this guy if I can nav to him
self - > lastEnemy = other ;
G_SetEnemy ( self , other ) ;
if ( self - > enemy ! = self - > lastEnemy )
{ //clear this so that we only sniff the player the first time we pick them up
self - > useDebounceTime = 0 ;
}
TIMER_Set ( self , " lookForNewEnemy " , Q_irand ( 5000 , 15000 ) ) ;
if ( hitByWampa )
{ //stay mad at this Wampa for 2-5 secs before looking for other enemies
TIMER_Set ( self , " wampaInfight " , Q_irand ( 2000 , 5000 ) ) ;
}
}
}
if ( ( hitByWampa | | Q_irand ( 0 , 100 ) < damage ) //hit by wampa, hit while holding live victim, or took a lot of damage
& & self - > client - > ps . legsAnim ! = BOTH_GESTURE1
& & self - > client - > ps . legsAnim ! = BOTH_GESTURE2
& & TIMER_Done ( self , " takingPain " ) )
{
if ( ! Wampa_CheckRoar ( self ) )
{
if ( self - > client - > ps . legsAnim ! = BOTH_ATTACK1
& & self - > client - > ps . legsAnim ! = BOTH_ATTACK2
& & self - > client - > ps . legsAnim ! = BOTH_ATTACK3 )
{ //cant interrupt one of the big attack anims
if ( self - > health > 100 | | hitByWampa )
{
TIMER_Remove ( self , " attacking " ) ;
VectorCopy ( self - > NPC - > lastPathAngles , self - > s . angles ) ;
if ( ! Q_irand ( 0 , 1 ) )
{
NPC_SetAnim ( self , SETANIM_BOTH , BOTH_PAIN2 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
else
{
NPC_SetAnim ( self , SETANIM_BOTH , BOTH_PAIN1 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
TIMER_Set ( self , " takingPain " , self - > client - > ps . legsAnimTimer + Q_irand ( 0 , 500 * ( 2 - g_spskill - > integer ) ) ) ;
TIMER_Set ( self , " attacking " , - level . time ) ;
//allow us to re-evaluate our running speed/anim
TIMER_Set ( self , " runfar " , - 1 ) ;
TIMER_Set ( self , " runclose " , - 1 ) ;
TIMER_Set ( self , " walk " , - 1 ) ;
if ( self - > NPC )
{
self - > NPC - > localState = LSTATE_WAITING ;
}
}
}
}
}
}
void Wampa_DropVictim ( gentity_t * self )
{
//FIXME: if Wampa dies, it should drop its victim.
//FIXME: if Wampa is removed, it must remove its victim.
//FIXME: if in BOTH_HOLD_DROP, throw them a little, too?
if ( self - > health > 0 )
{
NPC_SetAnim ( self , SETANIM_BOTH , BOTH_STAND2TO1 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
TIMER_Set ( self , " attacking " , - level . time ) ;
if ( self - > activator )
{
if ( self - > activator - > client )
{
self - > activator - > client - > ps . eFlags & = ~ EF_HELD_BY_WAMPA ;
}
self - > activator - > activator = NULL ;
NPC_SetAnim ( self - > activator , SETANIM_BOTH , BOTH_RELEASED , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
self - > activator - > client - > ps . legsAnimTimer + = 500 ;
self - > activator - > client - > ps . weaponTime = self - > activator - > client - > ps . torsoAnimTimer = self - > activator - > client - > ps . legsAnimTimer ;
if ( self - > activator - > health > 0 )
{
if ( self - > activator - > NPC )
{ //start thinking again
self - > activator - > NPC - > nextBStateThink = level . time ;
}
if ( self - > activator - > client & & self - > activator - > s . number < MAX_CLIENTS )
{
vec3_t vicAngles = { 30 , AngleNormalize180 ( self - > client - > ps . viewangles [ YAW ] + 180 ) , 0 } ;
SetClientViewAngle ( self - > activator , vicAngles ) ;
}
}
else
{
if ( self - > enemy = = self - > activator )
{
self - > enemy = NULL ;
}
self - > activator - > clipmask & = ~ CONTENTS_BODY ;
}
self - > activator = NULL ;
}
self - > count = 0 ; //drop him
}
qboolean Wampa_CheckDropVictim ( gentity_t * self , qboolean excludeMe )
{
if ( ! self
| | ! self - > activator )
{
return qtrue ;
}
vec3_t mins = { self - > activator - > mins [ 0 ] - 1 , self - > activator - > mins [ 1 ] - 1 , 0 } ;
vec3_t maxs = { self - > activator - > maxs [ 0 ] + 1 , self - > activator - > maxs [ 1 ] + 1 , 1 } ;
vec3_t start = { self - > activator - > currentOrigin [ 0 ] , self - > activator - > currentOrigin [ 1 ] , self - > activator - > absmin [ 2 ] } ;
vec3_t end = { self - > activator - > currentOrigin [ 0 ] , self - > activator - > currentOrigin [ 1 ] , self - > activator - > absmax [ 2 ] - 1 } ;
trace_t trace ;
if ( excludeMe )
{
gi . unlinkentity ( self ) ;
}
gi . trace ( & trace , start , mins , maxs , end , self - > activator - > s . number , self - > activator - > clipmask ) ;
if ( excludeMe )
{
gi . linkentity ( self ) ;
}
if ( ! trace . allsolid & & ! trace . startsolid & & trace . fraction > = 1.0f )
{
Wampa_DropVictim ( self ) ;
return qtrue ;
}
if ( excludeMe )
{ //victim stuck in wall
if ( self - > NPC )
{ //turn
self - > NPC - > desiredYaw + = Q_irand ( - 30 , 30 ) ;
self - > NPC - > lockedDesiredYaw = self - > NPC - > desiredYaw ;
}
}
return qfalse ;
}
extern float NPC_EnemyRangeFromBolt ( int boltIndex ) ;
qboolean Wampa_TryGrab ( void )
{
const float radius = 64.0f ;
if ( ! NPC - > enemy
| | ! NPC - > enemy - > client
| | NPC - > enemy - > health < = 0 )
{
return qfalse ;
}
float enemyDist = NPC_EnemyRangeFromBolt ( NPC - > handRBolt ) ;
if ( enemyDist < = radius
& & ! NPC - > count //don't have one in hand already
& & NPC - > enemy - > client - > NPC_class ! = CLASS_RANCOR
& & NPC - > enemy - > client - > NPC_class ! = CLASS_GALAKMECH
& & NPC - > enemy - > client - > NPC_class ! = CLASS_ATST
& & NPC - > enemy - > client - > NPC_class ! = CLASS_GONK
& & NPC - > enemy - > client - > NPC_class ! = CLASS_R2D2
& & NPC - > enemy - > client - > NPC_class ! = CLASS_R5D2
& & NPC - > enemy - > client - > NPC_class ! = CLASS_MARK1
& & NPC - > enemy - > client - > NPC_class ! = CLASS_MARK2
& & NPC - > enemy - > client - > NPC_class ! = CLASS_MOUSE
& & NPC - > enemy - > client - > NPC_class ! = CLASS_PROBE
& & NPC - > enemy - > client - > NPC_class ! = CLASS_SEEKER
& & NPC - > enemy - > client - > NPC_class ! = CLASS_REMOTE
& & NPC - > enemy - > client - > NPC_class ! = CLASS_SENTRY
& & NPC - > enemy - > client - > NPC_class ! = CLASS_INTERROGATOR
& & NPC - > enemy - > client - > NPC_class ! = CLASS_VEHICLE )
{ //grab
NPC - > enemy = NPC - > enemy ; //make him my new best friend
NPC - > enemy - > client - > ps . eFlags | = EF_HELD_BY_WAMPA ;
//FIXME: this makes it so that the victim can't hit us with shots! Just use activator or something
NPC - > enemy - > activator = NPC ; // kind of dumb, but when we are locked to the Rancor, we are owned by it.
NPC - > activator = NPC - > enemy ; //remember him
NPC - > count = 1 ; //in my hand
//wait to attack
TIMER_Set ( NPC , " attacking " , NPC - > client - > ps . legsAnimTimer + Q_irand ( 500 , 2500 ) ) ;
NPC_SetAnim ( NPC - > enemy , SETANIM_BOTH , BOTH_GRABBED , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_HOLD_END , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
TIMER_Set ( NPC , " takingPain " , - level . time ) ;
return qtrue ;
}
else if ( enemyDist < radius * 2.0f )
{ //smack
G_Sound ( NPC - > enemy , G_SoundIndex ( " sound/chars/rancor/swipehit.wav " ) ) ;
//actually push the enemy
vec3_t pushDir ;
vec3_t angs ;
VectorCopy ( NPC - > client - > ps . viewangles , angs ) ;
angs [ YAW ] + = Q_flrand ( 25 , 50 ) ;
angs [ PITCH ] = Q_flrand ( - 25 , - 15 ) ;
AngleVectors ( angs , pushDir , NULL , NULL ) ;
if ( NPC - > enemy - > client - > NPC_class ! = CLASS_RANCOR
& & NPC - > enemy - > client - > NPC_class ! = CLASS_ATST
& & ! ( NPC - > enemy - > flags & FL_NO_KNOCKBACK ) )
{
G_Throw ( NPC - > enemy , pushDir , Q_irand ( 30 , 70 ) ) ;
if ( NPC - > enemy - > health > 0 )
{ //do pain on enemy
G_Knockdown ( NPC - > enemy , NPC , pushDir , 300 , qtrue ) ;
}
}
}
return qfalse ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_BSWampa_Default
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
void NPC_BSWampa_Default ( void )
{
//NORMAL ANIMS
// stand1 = normal stand
// walk1 = normal, non-angry walk
// walk2 = injured
// run1 = far away run
// run2 = close run
//VICTIM ANIMS
// grabswipe = melee1 - sweep out and grab
// stand2 attack = attack4 - while holding victim, swipe at him
// walk3_drag = walk5 - walk with drag
// stand2 = hold victim
// stand2to1 = drop victim
if ( NPC - > client - > ps . legsAnim = = BOTH_HOLD_START )
{
NPC_FaceEnemy ( qtrue ) ;
if ( NPC - > client - > ps . legsAnimTimer < 200 )
{ //see if he's there to grab
if ( ! Wampa_TryGrab ( ) )
{
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_HOLD_MISS , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
}
return ;
}
if ( NPC - > count )
{
if ( ! NPC - > activator
| | ! NPC - > activator - > client )
{ //wtf?
NPC - > count = 0 ;
NPC - > activator = NULL ;
}
else
{
if ( NPC - > client - > ps . legsAnim = = BOTH_HOLD_DROP )
{
if ( NPC - > client - > ps . legsAnimTimer < PM_AnimLength ( NPC - > client - > clientInfo . animFileIndex , ( animNumber_t ) NPC - > client - > ps . legsAnim ) - 500 )
{ //at least half a second into the anim
if ( Wampa_CheckDropVictim ( NPC , qfalse ) )
{
TIMER_Set ( NPC , " attacking " , 1000 + ( Q_irand ( 500 , 1000 ) * ( 3 - g_spskill - > integer ) ) ) ;
}
}
}
else if ( ! TIMER_Done ( NPC , " takingPain " ) )
{
Wampa_CheckDropVictim ( NPC , qfalse ) ;
}
else if ( NPC - > activator - > health < = 0 )
{
if ( TIMER_Done ( NPC , " sniffCorpse " ) )
{
Wampa_CheckDropVictim ( NPC , qfalse ) ;
}
}
else if ( NPC - > useDebounceTime > = level . time
& & NPC - > activator )
{ //just sniffing the guy
if ( NPC - > useDebounceTime < = level . time + 100
& & NPC - > client - > ps . legsAnim ! = BOTH_HOLD_DROP )
{ //just about done, drop him
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_HOLD_DROP , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
TIMER_Set ( NPC , " attacking " , NPC - > client - > ps . legsAnimTimer + 500 ) ;
}
}
else
{
if ( ! NPC - > useDebounceTime
& & NPC - > activator
& & NPC - > activator - > s . number < MAX_CLIENTS )
{ //first time I pick the player, just sniff them
if ( TIMER_Done ( NPC , " attacking " ) )
{ //ready to attack
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_HOLD_SNIFF , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
NPC - > useDebounceTime = level . time + NPC - > client - > ps . legsAnimTimer + Q_irand ( 500 , 2000 ) ;
}
}
else
{
if ( TIMER_Done ( NPC , " attacking " ) )
{ //ready to attack
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_HOLD_ATTACK /*BOTH_ATTACK4*/ , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
TIMER_Set ( NPC , " grabAttackDamage " , 1400 ) ;
TIMER_Set ( NPC , " attacking " , NPC - > client - > ps . legsAnimTimer + Q_irand ( 3000 , 10000 ) ) ;
}
if ( NPC - > client - > ps . legsAnim = = BOTH_HOLD_ATTACK )
{
if ( NPC - > client - > ps . legsAnimTimer )
{
if ( TIMER_Done2 ( NPC , " grabAttackDamage " , qtrue ) )
{
G_Sound ( NPC - > activator , G_SoundIndex ( " sound/chars/rancor/swipehit.wav " ) ) ;
G_Damage ( NPC - > activator , NPC , NPC , vec3_origin , NPC - > activator - > currentOrigin , Q_irand ( 25 , 40 ) , ( DAMAGE_NO_KNOCKBACK | DAMAGE_NO_ARMOR ) , MOD_MELEE ) ;
if ( NPC - > activator - > health < = 0 )
{ //killed them, chance of dismembering
int hitLoc = HL_WAIST ;
if ( g_dismemberment - > integer < 4 )
{
hitLoc = Q_irand ( HL_BACK_RT , HL_HAND_LT ) ;
}
else
{
hitLoc = Q_irand ( HL_WAIST , HL_HEAD ) ;
}
NPC - > activator - > client - > dismembered = false ;
//FIXME: the limb should just disappear, cuz I ate it
G_DoDismemberment ( NPC - > activator , NPC - > activator - > currentOrigin , MOD_SABER , 1000 , hitLoc , qtrue ) ;
TIMER_Set ( NPC , " sniffCorpse " , Q_irand ( 2000 , 5000 ) ) ;
}
NPC_SetAnim ( NPC - > activator , SETANIM_BOTH , BOTH_HANG_PAIN , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
}
else
{
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_HOLD_IDLE /*BOTH_ATTACK4*/ , SETANIM_FLAG_NORMAL ) ;
}
}
else if ( NPC - > client - > ps . legsAnim = = BOTH_STAND2TO1
& & ! NPC - > client - > ps . legsAnimTimer )
{
NPC_SetAnim ( NPC , SETANIM_BOTH , BOTH_HOLD_IDLE , SETANIM_FLAG_NORMAL ) ;
}
}
}
}
NPC_UpdateAngles ( qtrue , qtrue ) ;
return ;
}
if ( NPCInfo - > localState = = LSTATE_WAITING
& & TIMER_Done2 ( NPC , " takingPain " , qtrue ) )
{ //was not doing anything because we were taking pain, but pain is done now, so clear it...
NPCInfo - > localState = LSTATE_CLEAR ;
}
if ( ! TIMER_Done ( NPC , " rageTime " ) )
{ //do nothing but roar first time we see an enemy
NPC_FaceEnemy ( qtrue ) ;
return ;
}
if ( NPC - > enemy )
{
if ( NPC - > enemy - > client //enemy is a client
& & ( NPC - > enemy - > client - > NPC_class = = CLASS_UGNAUGHT | | NPC - > enemy - > client - > NPC_class = = CLASS_JAWA ) //enemy is a lowly jawa or ugnaught
& & NPC - > enemy - > enemy ! = NPC //enemy's enemy is not me
& & ( ! NPC - > enemy - > enemy | | ! NPC - > enemy - > enemy - > client | | NPC - > enemy - > enemy - > client - > NPC_class ! = CLASS_RANCOR ) ) //enemy's enemy is not a client or is not a rancor (which is scarier than me)
{ //they should be scared of ME and no-one else
G_SetEnemy ( NPC - > enemy , NPC ) ;
}
if ( ! TIMER_Done ( NPC , " attacking " ) )
{ //in middle of attack
//face enemy
NPC_FaceEnemy ( qtrue ) ;
//continue attack logic
enemyDist = Distance ( NPC - > currentOrigin , NPC - > enemy - > currentOrigin ) ;
Wampa_Attack ( enemyDist , qfalse ) ;
return ;
}
else
{
if ( TIMER_Done ( NPC , " angrynoise " ) )
{
G_SoundOnEnt ( NPC , CHAN_AUTO , va ( " sound/chars/wampa/misc/anger%d.wav " , Q_irand ( 1 , 2 ) ) ) ;
TIMER_Set ( NPC , " angrynoise " , Q_irand ( 5000 , 10000 ) ) ;
}
//else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while
if ( NPC - > enemy - > client & & NPC - > enemy - > client - > NPC_class = = CLASS_WAMPA )
{ //got mad at another Wampa, look for a valid enemy
if ( TIMER_Done ( NPC , " wampaInfight " ) )
{
NPC_CheckEnemyExt ( qtrue ) ;
}
}
else
{
if ( NPC_ValidEnemy ( NPC - > enemy ) = = qfalse )
{
TIMER_Remove ( NPC , " lookForNewEnemy " ) ; //make them look again right now
if ( ! NPC - > enemy - > inuse | | level . time - NPC - > enemy - > s . time > Q_irand ( 10000 , 15000 ) )
{ //it's been a while since the enemy died, or enemy is completely gone, get bored with him
NPC - > enemy = NULL ;
Wampa_Patrol ( ) ;
NPC_UpdateAngles ( qtrue , qtrue ) ;
return ;
}
}
if ( TIMER_Done ( NPC , " lookForNewEnemy " ) )
{
gentity_t * sav_enemy = NPC - > enemy ; //FIXME: what about NPC->lastEnemy?
NPC - > enemy = NULL ;
gentity_t * newEnemy = NPC_CheckEnemy ( NPCInfo - > confusionTime < level . time , qfalse , qfalse ) ;
NPC - > enemy = sav_enemy ;
if ( newEnemy & & newEnemy ! = sav_enemy )
{ //picked up a new enemy!
NPC - > lastEnemy = NPC - > enemy ;
G_SetEnemy ( NPC , newEnemy ) ;
if ( NPC - > enemy ! = NPC - > lastEnemy )
{ //clear this so that we only sniff the player the first time we pick them up
NPC - > useDebounceTime = 0 ;
}
//hold this one for at least 5-15 seconds
TIMER_Set ( NPC , " lookForNewEnemy " , Q_irand ( 5000 , 15000 ) ) ;
}
else
{ //look again in 2-5 secs
TIMER_Set ( NPC , " lookForNewEnemy " , Q_irand ( 2000 , 5000 ) ) ;
}
}
}
Wampa_Combat ( ) ;
return ;
}
}
else
{
if ( TIMER_Done ( NPC , " idlenoise " ) )
{
G_SoundOnEnt ( NPC , CHAN_AUTO , " sound/chars/wampa/misc/anger3.wav " ) ;
TIMER_Set ( NPC , " idlenoise " , Q_irand ( 2000 , 4000 ) ) ;
}
if ( NPCInfo - > scriptFlags & SCF_LOOK_FOR_ENEMIES )
{
Wampa_Patrol ( ) ;
}
else
{
Wampa_Idle ( ) ;
}
}
NPC_UpdateAngles ( qtrue , qtrue ) ;
}