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"
|
|
|
|
|
|
|
|
extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
|
|
|
|
|
|
|
|
#define MIN_ATTACK_DIST_SQ 128
|
|
|
|
#define MIN_MISS_DIST 100
|
|
|
|
#define MIN_MISS_DIST_SQ (MIN_MISS_DIST*MIN_MISS_DIST)
|
|
|
|
#define MAX_MISS_DIST 500
|
|
|
|
#define MAX_MISS_DIST_SQ (MAX_MISS_DIST*MAX_MISS_DIST)
|
|
|
|
#define MIN_SCORE -37500 //speed of (50*50) - dist of (200*200)
|
|
|
|
|
|
|
|
void SandCreature_Precache( void )
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
G_EffectIndex( "env/sand_dive" );
|
|
|
|
G_EffectIndex( "env/sand_spray" );
|
|
|
|
G_EffectIndex( "env/sand_move" );
|
|
|
|
G_EffectIndex( "env/sand_move_breach" );
|
|
|
|
//G_EffectIndex( "env/sand_attack_breach" );
|
|
|
|
for ( i = 1; i < 4; i++ )
|
|
|
|
{
|
|
|
|
G_SoundIndex( va( "sound/chars/sand_creature/voice%d.mp3", i ) );
|
|
|
|
}
|
|
|
|
G_SoundIndex( "sound/chars/sand_creature/slither.wav" );
|
|
|
|
}
|
|
|
|
|
|
|
|
void SandCreature_ClearTimers( gentity_t *ent )
|
|
|
|
{
|
|
|
|
TIMER_Set( NPC, "speaking", -level.time );
|
|
|
|
TIMER_Set( NPC, "breaching", -level.time );
|
|
|
|
TIMER_Set( NPC, "breachDebounce", -level.time );
|
|
|
|
TIMER_Set( NPC, "pain", -level.time );
|
|
|
|
TIMER_Set( NPC, "attacking", -level.time );
|
|
|
|
TIMER_Set( NPC, "missDebounce", -level.time );
|
|
|
|
}
|
|
|
|
|
|
|
|
void NPC_SandCreature_Die( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
|
|
|
|
{
|
|
|
|
//FIXME: somehow make him solid when he dies?
|
|
|
|
}
|
|
|
|
|
|
|
|
void NPC_SandCreature_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
|
|
|
|
{
|
|
|
|
if ( TIMER_Done( self, "pain" ) )
|
|
|
|
{
|
|
|
|
//FIXME: effect and sound
|
|
|
|
//FIXME: shootable during this anim?
|
|
|
|
NPC_SetAnim( self, SETANIM_LEGS, Q_irand(BOTH_ATTACK1,BOTH_ATTACK2), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
|
|
|
|
G_AddEvent( self, EV_PAIN, Q_irand( 0, 100 ) );
|
|
|
|
TIMER_Set( self, "pain", self->client->ps.legsAnimTimer + Q_irand( 500, 2000 ) );
|
|
|
|
float playerDist = Distance( player->currentOrigin, self->currentOrigin );
|
|
|
|
if ( playerDist < 256 )
|
|
|
|
{
|
|
|
|
CGCam_Shake( 1.0f*playerDist/128.0f, self->client->ps.legsAnimTimer );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self->enemy = self->NPC->goalEntity = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SandCreature_MoveEffect( void )
|
|
|
|
{
|
|
|
|
vec3_t up = {0,0,1};
|
|
|
|
vec3_t org = {NPC->currentOrigin[0], NPC->currentOrigin[1], NPC->absmin[2]+2};
|
|
|
|
|
|
|
|
float playerDist = Distance( player->currentOrigin, NPC->currentOrigin );
|
|
|
|
if ( playerDist < 256 )
|
|
|
|
{
|
|
|
|
CGCam_Shake( 0.75f*playerDist/256.0f, 250 );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( level.time-NPC->client->ps.lastStationary > 2000 )
|
|
|
|
{//first time moving for at least 2 seconds
|
|
|
|
//clear speakingtime
|
|
|
|
TIMER_Set( NPC, "speaking", -level.time );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( TIMER_Done( NPC, "breaching" )
|
|
|
|
&& TIMER_Done( NPC, "breachDebounce" )
|
|
|
|
&& TIMER_Done( NPC, "pain" )
|
|
|
|
&& TIMER_Done( NPC, "attacking" )
|
|
|
|
&& !Q_irand( 0, 10 ) )
|
|
|
|
{//Breach!
|
|
|
|
//FIXME: only do this while moving forward?
|
|
|
|
trace_t trace;
|
|
|
|
//make him solid here so he can be hit/gets blocked on stuff. Check clear first.
|
|
|
|
gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, NPC->currentOrigin, NPC->s.number, MASK_NPCSOLID );
|
|
|
|
if ( !trace.allsolid && !trace.startsolid )
|
|
|
|
{
|
|
|
|
NPC->clipmask = MASK_NPCSOLID;//turn solid for a little bit
|
|
|
|
NPC->contents = CONTENTS_BODY;
|
|
|
|
//NPC->takedamage = qtrue;//can be shot?
|
|
|
|
|
|
|
|
//FIXME: Breach sound?
|
|
|
|
//FIXME: Breach effect?
|
|
|
|
NPC_SetAnim( NPC, SETANIM_LEGS, BOTH_WALK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
|
|
|
|
TIMER_Set( NPC, "breaching", NPC->client->ps.legsAnimTimer );
|
|
|
|
TIMER_Set( NPC, "breachDebounce", NPC->client->ps.legsAnimTimer+Q_irand( 0, 10000 ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( !TIMER_Done( NPC, "breaching" ) )
|
|
|
|
{//different effect when breaching
|
|
|
|
//FIXME: make effect
|
|
|
|
G_PlayEffect( G_EffectIndex( "env/sand_move_breach" ), org, up );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
G_PlayEffect( G_EffectIndex( "env/sand_move" ), org, up );
|
|
|
|
}
|
|
|
|
NPC->s.loopSound = G_SoundIndex( "sound/chars/sand_creature/slither.wav" );
|
|
|
|
}
|
|
|
|
|
|
|
|
qboolean SandCreature_CheckAhead( vec3_t end )
|
|
|
|
{
|
|
|
|
trace_t trace;
|
|
|
|
int clipmask = NPC->clipmask|CONTENTS_BOTCLIP;
|
|
|
|
|
|
|
|
//make sure our goal isn't underground (else the trace will fail)
|
|
|
|
vec3_t bottom = {end[0],end[1],end[2]+NPC->mins[2]};
|
|
|
|
gi.trace( &trace, end, vec3_origin, vec3_origin, bottom, NPC->s.number, NPC->clipmask );
|
|
|
|
if ( trace.fraction < 1.0f )
|
|
|
|
{//in the ground, raise it up
|
|
|
|
end[2] -= NPC->mins[2]*(1.0f-trace.fraction)-0.125f;
|
|
|
|
}
|
|
|
|
|
|
|
|
gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask );
|
|
|
|
|
|
|
|
if ( trace.startsolid&&(trace.contents&CONTENTS_BOTCLIP) )
|
|
|
|
{//started inside do not enter, so ignore them
|
|
|
|
clipmask &= ~CONTENTS_BOTCLIP;
|
|
|
|
gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask );
|
|
|
|
}
|
|
|
|
//Do a simple check
|
|
|
|
if ( ( trace.allsolid == qfalse ) && ( trace.startsolid == qfalse ) && ( trace.fraction == 1.0f ) )
|
|
|
|
return qtrue;
|
|
|
|
|
|
|
|
if ( trace.plane.normal[2] >= MIN_WALK_NORMAL )
|
|
|
|
{
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
//This is a work around
|
|
|
|
float radius = ( NPC->maxs[0] > NPC->maxs[1] ) ? NPC->maxs[0] : NPC->maxs[1];
|
|
|
|
float dist = Distance( NPC->currentOrigin, end );
|
|
|
|
float tFrac = 1.0f - ( radius / dist );
|
|
|
|
|
|
|
|
if ( trace.fraction >= tFrac )
|
|
|
|
return qtrue;
|
|
|
|
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
qboolean SandCreature_Move( void )
|
|
|
|
{
|
|
|
|
qboolean moved = qfalse;
|
|
|
|
//FIXME should ignore doors..?
|
|
|
|
vec3_t dest;
|
|
|
|
VectorCopy( NPCInfo->goalEntity->currentOrigin, dest );
|
|
|
|
//Sand Creatures look silly using waypoints when they can go straight to the goal
|
|
|
|
if ( SandCreature_CheckAhead( dest ) )
|
|
|
|
{//use our temp move straight to goal check
|
|
|
|
VectorSubtract( dest, NPC->currentOrigin, NPC->client->ps.moveDir );
|
|
|
|
NPC->client->ps.speed = VectorNormalize( NPC->client->ps.moveDir );
|
|
|
|
if ( (ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed > NPCInfo->stats.walkSpeed )
|
|
|
|
{
|
|
|
|
NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( NPC->client->ps.speed < NPCInfo->stats.walkSpeed )
|
|
|
|
{
|
|
|
|
NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
|
|
|
|
}
|
|
|
|
if ( !(ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed < NPCInfo->stats.runSpeed )
|
|
|
|
{
|
|
|
|
NPC->client->ps.speed = NPCInfo->stats.runSpeed;
|
|
|
|
}
|
|
|
|
else if ( NPC->client->ps.speed > NPCInfo->stats.runSpeed )
|
|
|
|
{
|
|
|
|
NPC->client->ps.speed = NPCInfo->stats.runSpeed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
moved = qtrue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
moved = NPC_MoveToGoal( qtrue );
|
|
|
|
}
|
|
|
|
if ( moved && NPC->radius )
|
|
|
|
{
|
|
|
|
vec3_t newPos;
|
|
|
|
float curTurfRange, newTurfRange;
|
|
|
|
curTurfRange = DistanceHorizontal( NPC->currentOrigin, NPC->s.origin );
|
|
|
|
VectorMA( NPC->currentOrigin, NPC->client->ps.speed/100.0f, NPC->client->ps.moveDir, newPos );
|
|
|
|
newTurfRange = DistanceHorizontal( newPos, NPC->s.origin );
|
|
|
|
if ( newTurfRange > NPC->radius && newTurfRange > curTurfRange )
|
|
|
|
{//would leave our range
|
|
|
|
//stop
|
|
|
|
NPC->client->ps.speed = 0.0f;
|
|
|
|
VectorClear( NPC->client->ps.moveDir );
|
|
|
|
ucmd.forwardmove = ucmd.rightmove = 0;
|
|
|
|
moved = qfalse;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (moved);
|
|
|
|
//often erroneously returns false ??? something wrong with NAV...?
|
|
|
|
}
|
|
|
|
|
|
|
|
void SandCreature_Attack( qboolean miss )
|
|
|
|
{
|
|
|
|
//FIXME: make it able to grab a thermal detonator, take it down,
|
|
|
|
// then have it explode inside them, killing them
|
|
|
|
// (or, do damage, making them stick half out of the ground and
|
|
|
|
// screech for a bit, giving you a chance to run for it!)
|
|
|
|
|
|
|
|
//FIXME: effect and sound
|
|
|
|
//FIXME: shootable during this anim?
|
|
|
|
if ( !NPC->enemy->client )
|
|
|
|
{
|
|
|
|
NPC_SetAnim( NPC, SETANIM_LEGS, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
NPC_SetAnim( NPC, SETANIM_LEGS, Q_irand( BOTH_ATTACK1, BOTH_ATTACK2 ), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
|
|
|
|
}
|
|
|
|
//don't do anything else while in this anim
|
|
|
|
TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer );
|
|
|
|
float playerDist = Distance( player->currentOrigin, NPC->currentOrigin );
|
|
|
|
if ( playerDist < 256 )
|
|
|
|
{
|
|
|
|
//FIXME: tone this down
|
|
|
|
CGCam_Shake( 0.75f*playerDist/128.0f, NPC->client->ps.legsAnimTimer );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( miss )
|
|
|
|
{//purposely missed him, chance of knocking him down
|
|
|
|
//FIXME: if, during the attack anim, I do end up catching him close to my mouth, then snatch him anyway...
|
|
|
|
if ( NPC->enemy && NPC->enemy->client )
|
|
|
|
{
|
|
|
|
vec3_t dir2Enemy;
|
|
|
|
VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, dir2Enemy );
|
|
|
|
if ( dir2Enemy[2] < 30 )
|
|
|
|
{
|
|
|
|
dir2Enemy[2] = 30;
|
|
|
|
}
|
|
|
|
if ( g_spskill->integer > 0 )
|
|
|
|
{
|
|
|
|
float enemyDist = VectorNormalize( dir2Enemy );
|
|
|
|
//FIXME: tone this down, smaller radius
|
|
|
|
if ( enemyDist < 200 && NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
|
|
|
|
{
|
|
|
|
float throwStr = ((200-enemyDist)*0.4f)+20;
|
|
|
|
if ( throwStr > 45 )
|
|
|
|
{
|
|
|
|
throwStr = 45;
|
|
|
|
}
|
|
|
|
G_Throw( NPC->enemy, dir2Enemy, throwStr );
|
|
|
|
if ( g_spskill->integer > 1 )
|
|
|
|
{//knock them down, too
|
|
|
|
if ( NPC->enemy->health > 0
|
|
|
|
&& Q_flrand( 50, 150 ) > enemyDist )
|
|
|
|
{//knock them down
|
|
|
|
G_Knockdown( NPC->enemy, NPC, dir2Enemy, 300, qtrue );
|
|
|
|
if ( NPC->enemy->s.number < MAX_CLIENTS )
|
|
|
|
{//make the player look up at me
|
|
|
|
vec3_t vAng;
|
|
|
|
vectoangles( dir2Enemy, vAng );
|
|
|
|
VectorSet( vAng, AngleNormalize180(vAng[PITCH])*-1, NPC->enemy->client->ps.viewangles[YAW], 0 );
|
|
|
|
SetClientViewAngle( NPC->enemy, vAng );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
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
|
|
|
|
//this guy isn't going anywhere anymore
|
|
|
|
NPC->enemy->contents = 0;
|
|
|
|
NPC->enemy->clipmask = 0;
|
|
|
|
|
|
|
|
if ( NPC->activator->client )
|
|
|
|
{
|
|
|
|
NPC->activator->client->ps.SaberDeactivate();
|
|
|
|
NPC->activator->client->ps.eFlags |= EF_HELD_BY_SAND_CREATURE;
|
|
|
|
if ( NPC->activator->health > 0 && NPC->activator->client )
|
|
|
|
{
|
|
|
|
G_AddEvent( NPC->activator, Q_irand(EV_DEATH1, EV_DEATH3), 0 );
|
|
|
|
NPC_SetAnim( NPC->activator, SETANIM_LEGS, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
|
|
|
|
NPC_SetAnim( NPC->activator, SETANIM_TORSO, BOTH_FALLDEATH1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
|
|
|
|
TossClientItems( NPC );
|
|
|
|
if ( NPC->activator->NPC )
|
|
|
|
{//no more thinking for you
|
|
|
|
NPC->activator->NPC->nextBStateThink = Q3_INFINITE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
if ( !NPC->activator->s.number )
|
|
|
|
{
|
|
|
|
cg.overrides.active |= (CG_OVERRIDE_3RD_PERSON_CDP|CG_OVERRIDE_3RD_PERSON_RNG);
|
|
|
|
cg.overrides.thirdPersonCameraDamp = 0;
|
|
|
|
cg.overrides.thirdPersonRange = 120;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
NPC->activator->s.eFlags |= EF_HELD_BY_SAND_CREATURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
float SandCreature_EntScore( gentity_t *ent )
|
|
|
|
{
|
|
|
|
float moveSpeed, dist;
|
|
|
|
|
|
|
|
if ( ent->client )
|
|
|
|
{
|
|
|
|
moveSpeed = VectorLengthSquared( ent->client->ps.velocity );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
moveSpeed = VectorLengthSquared( ent->s.pos.trDelta );
|
|
|
|
}
|
|
|
|
dist = DistanceSquared( NPC->currentOrigin, ent->currentOrigin );
|
|
|
|
return (moveSpeed-dist);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SandCreature_SeekEnt( gentity_t *bestEnt, float score )
|
|
|
|
{
|
|
|
|
NPCInfo->enemyLastSeenTime = level.time;
|
|
|
|
VectorCopy( bestEnt->currentOrigin, NPCInfo->enemyLastSeenLocation );
|
|
|
|
NPC_SetMoveGoal( NPC, NPCInfo->enemyLastSeenLocation, 0, qfalse );
|
|
|
|
if ( score > MIN_SCORE )
|
|
|
|
{
|
|
|
|
NPC->enemy = bestEnt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SandCreature_CheckMovingEnts( void )
|
|
|
|
{
|
|
|
|
gentity_t *radiusEnts[ 128 ];
|
|
|
|
int numEnts;
|
|
|
|
const float radius = NPCInfo->stats.earshot;
|
|
|
|
int i;
|
|
|
|
vec3_t mins, maxs;
|
|
|
|
|
|
|
|
for ( i = 0; i < 3; i++ )
|
|
|
|
{
|
|
|
|
mins[i] = NPC->currentOrigin[i] - radius;
|
|
|
|
maxs[i] = NPC->currentOrigin[i] + radius;
|
|
|
|
}
|
|
|
|
|
|
|
|
numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, 128 );
|
|
|
|
int bestEnt = -1;
|
|
|
|
float bestScore = 0;
|
|
|
|
float checkScore;
|
|
|
|
|
|
|
|
for ( i = 0; i < numEnts; i++ )
|
|
|
|
{
|
|
|
|
if ( !radiusEnts[i]->inuse )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( radiusEnts[i] == NPC )
|
|
|
|
{//Skip the rancor ent
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( radiusEnts[i]->client == NULL )
|
|
|
|
{//must be a client
|
|
|
|
if ( radiusEnts[i]->s.eType != ET_MISSILE
|
|
|
|
|| radiusEnts[i]->s.weapon != WP_THERMAL )
|
|
|
|
{//not a thermal detonator
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) )
|
|
|
|
{//can't be one being held
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_WAMPA) )
|
|
|
|
{//can't be one being held
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE) )
|
|
|
|
{//can't be one being held
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) )
|
|
|
|
{//not if invisible
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_WORLD )
|
|
|
|
{//not on the ground
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( radiusEnts[i]->client->NPC_class == CLASS_SAND_CREATURE )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( (radiusEnts[i]->flags&FL_NOTARGET) )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
if ( radiusEnts[i]->client && (radiusEnts[i]->client->NPC_class == CLASS_RANCOR || radiusEnts[i]->client->NPC_class == CLASS_ATST ) )
|
|
|
|
{//can't grab rancors or atst's
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
checkScore = SandCreature_EntScore( radiusEnts[i] );
|
|
|
|
//FIXME: take mass into account too? What else?
|
|
|
|
if ( checkScore > bestScore )
|
|
|
|
{
|
|
|
|
bestScore = checkScore;
|
|
|
|
bestEnt = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( bestEnt != -1 )
|
|
|
|
{
|
|
|
|
SandCreature_SeekEnt( radiusEnts[bestEnt], bestScore );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SandCreature_SeekAlert( int alertEvent )
|
|
|
|
{
|
|
|
|
alertEvent_t *alert = &level.alertEvents[alertEvent];
|
|
|
|
|
|
|
|
//FIXME: check for higher alert status or closer than last location?
|
|
|
|
NPCInfo->enemyLastSeenTime = level.time;
|
|
|
|
VectorCopy( alert->position, NPCInfo->enemyLastSeenLocation );
|
|
|
|
NPC_SetMoveGoal( NPC, NPCInfo->enemyLastSeenLocation, 0, qfalse );
|
|
|
|
}
|
|
|
|
|
|
|
|
void SandCreature_CheckAlerts( void )
|
|
|
|
{
|
|
|
|
if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
|
|
|
|
{
|
|
|
|
int alertEvent = NPC_CheckAlertEvents( qfalse, qtrue, NPCInfo->lastAlertID, qfalse, AEL_MINOR, qtrue );
|
|
|
|
|
|
|
|
//There is an event to look at
|
|
|
|
if ( alertEvent >= 0 )
|
|
|
|
{
|
|
|
|
//if ( level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
|
|
|
|
{
|
|
|
|
SandCreature_SeekAlert( alertEvent );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
float SandCreature_DistSqToGoal( qboolean goalIsEnemy )
|
|
|
|
{
|
|
|
|
float goalDistSq;
|
|
|
|
if ( !NPCInfo->goalEntity || goalIsEnemy )
|
|
|
|
{
|
|
|
|
if ( !NPC->enemy )
|
|
|
|
{
|
|
|
|
return Q3_INFINITE;
|
|
|
|
}
|
|
|
|
NPCInfo->goalEntity = NPC->enemy;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( NPCInfo->goalEntity->client )
|
|
|
|
{
|
|
|
|
goalDistSq = DistanceSquared( NPC->currentOrigin, NPCInfo->goalEntity->currentOrigin );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
vec3_t gOrg;
|
|
|
|
VectorCopy( NPCInfo->goalEntity->currentOrigin, gOrg );
|
|
|
|
gOrg[2] -= (NPC->mins[2]-NPCInfo->goalEntity->mins[2]);//moves the gOrg up/down to make it's origin seem at the proper height as if it had my mins
|
|
|
|
goalDistSq = DistanceSquared( NPC->currentOrigin, gOrg );
|
|
|
|
}
|
|
|
|
return goalDistSq;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SandCreature_Chase( void )
|
|
|
|
{
|
|
|
|
if ( !NPC->enemy->inuse )
|
|
|
|
{//freed
|
|
|
|
NPC->enemy = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( (NPC->svFlags&SVF_LOCKEDENEMY) )
|
|
|
|
{//always know where he is
|
|
|
|
NPCInfo->enemyLastSeenTime = level.time;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !(NPC->svFlags&SVF_LOCKEDENEMY) )
|
|
|
|
{
|
|
|
|
if ( level.time-NPCInfo->enemyLastSeenTime > 10000 )
|
|
|
|
{
|
|
|
|
NPC->enemy = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( NPC->enemy->client )
|
|
|
|
{
|
|
|
|
if ( (NPC->enemy->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE)
|
|
|
|
|| (NPC->enemy->client->ps.eFlags&EF_HELD_BY_RANCOR)
|
|
|
|
|| (NPC->enemy->client->ps.eFlags&EF_HELD_BY_WAMPA) )
|
|
|
|
{//was picked up by another monster, forget about him
|
|
|
|
NPC->enemy = NULL;
|
|
|
|
NPC->svFlags &= ~SVF_LOCKEDENEMY;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//chase the enemy
|
|
|
|
if ( NPC->enemy->client
|
|
|
|
&& NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_WORLD
|
|
|
|
&& !(NPC->svFlags&SVF_LOCKEDENEMY) )
|
|
|
|
{//off the ground!
|
|
|
|
//FIXME: keep moving in the dir we were moving for a little bit...
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
float enemyScore = SandCreature_EntScore( NPC->enemy );
|
|
|
|
if ( enemyScore < MIN_SCORE
|
|
|
|
&& !(NPC->svFlags&SVF_LOCKEDENEMY) )
|
|
|
|
{//too slow or too far away
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
float moveSpeed;
|
|
|
|
if ( NPC->enemy->client )
|
|
|
|
{
|
|
|
|
moveSpeed = VectorLengthSquared( NPC->enemy->client->ps.velocity );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
moveSpeed = VectorLengthSquared( NPC->enemy->s.pos.trDelta );
|
|
|
|
}
|
|
|
|
if ( moveSpeed )
|
|
|
|
{//he's still moving, update my goalEntity's origin
|
|
|
|
SandCreature_SeekEnt( NPC->enemy, 0 );
|
|
|
|
NPCInfo->enemyLastSeenTime = level.time;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( (level.time-NPCInfo->enemyLastSeenTime) > 5000
|
|
|
|
&& !(NPC->svFlags&SVF_LOCKEDENEMY) )
|
|
|
|
{//enemy hasn't moved in about 5 seconds, see if there's anything else of interest
|
|
|
|
SandCreature_CheckAlerts();
|
|
|
|
SandCreature_CheckMovingEnts();
|
|
|
|
}
|
|
|
|
|
|
|
|
float enemyDistSq = SandCreature_DistSqToGoal( qtrue );
|
|
|
|
|
|
|
|
//FIXME: keeps chasing goalEntity even when it's already reached it...?
|
|
|
|
if ( enemyDistSq >= MIN_ATTACK_DIST_SQ//NPCInfo->goalEntity &&
|
|
|
|
&& (level.time-NPCInfo->enemyLastSeenTime) <= 3000 )
|
|
|
|
{//sensed enemy (or something) less than 3 seconds ago
|
|
|
|
ucmd.buttons &= ~BUTTON_WALKING;
|
|
|
|
if ( SandCreature_Move() )
|
|
|
|
{
|
|
|
|
SandCreature_MoveEffect();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( (level.time-NPCInfo->enemyLastSeenTime) <= 5000
|
|
|
|
&& !(NPC->svFlags&SVF_LOCKEDENEMY) )
|
|
|
|
{//NOTE: this leaves a 2-second dead zone in which they'll just sit there unless their enemy moves
|
|
|
|
//If there is an event we might be interested in if we weren't still interested in our enemy
|
|
|
|
if ( NPC_CheckAlertEvents( qfalse, qtrue, NPCInfo->lastAlertID, qfalse, AEL_MINOR, qtrue ) >= 0 )
|
|
|
|
{//just stir
|
|
|
|
SandCreature_MoveEffect();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( enemyDistSq < MIN_ATTACK_DIST_SQ )
|
|
|
|
{
|
|
|
|
if ( NPC->enemy->client )
|
|
|
|
{
|
|
|
|
NPC->client->ps.viewangles[YAW] = NPC->enemy->client->ps.viewangles[YAW];
|
|
|
|
}
|
|
|
|
if ( TIMER_Done( NPC, "breaching" ) )
|
|
|
|
{
|
|
|
|
//okay to attack
|
|
|
|
SandCreature_Attack( qfalse );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( enemyDistSq < MAX_MISS_DIST_SQ
|
|
|
|
&& enemyDistSq > MIN_MISS_DIST_SQ
|
|
|
|
&& NPC->enemy->client
|
|
|
|
&& TIMER_Done( NPC, "breaching" )
|
|
|
|
&& TIMER_Done( NPC, "missDebounce" )
|
|
|
|
&& !VectorCompare( NPC->pos1, NPC->currentOrigin ) //so we don't come up again in the same spot
|
|
|
|
&& !Q_irand( 0, 10 ) )
|
|
|
|
{
|
|
|
|
if ( !(NPC->svFlags&SVF_LOCKEDENEMY) )
|
|
|
|
{
|
|
|
|
//miss them
|
|
|
|
SandCreature_Attack( qtrue );
|
|
|
|
VectorCopy( NPC->currentOrigin, NPC->pos1 );
|
|
|
|
TIMER_Set( NPC, "missDebounce", Q_irand( 3000, 10000 ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SandCreature_Hunt( void )
|
|
|
|
{
|
|
|
|
SandCreature_CheckAlerts();
|
|
|
|
SandCreature_CheckMovingEnts();
|
|
|
|
//If we have somewhere to go, then do that
|
|
|
|
//FIXME: keeps chasing goalEntity even when it's already reached it...?
|
|
|
|
if ( NPCInfo->goalEntity
|
|
|
|
&& SandCreature_DistSqToGoal( qfalse ) >= MIN_ATTACK_DIST_SQ )
|
|
|
|
{
|
|
|
|
ucmd.buttons |= BUTTON_WALKING;
|
|
|
|
if ( SandCreature_Move() )
|
|
|
|
{
|
|
|
|
SandCreature_MoveEffect();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
NPC_ReachedGoal();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SandCreature_Sleep( void )
|
|
|
|
{
|
|
|
|
SandCreature_CheckAlerts();
|
|
|
|
SandCreature_CheckMovingEnts();
|
|
|
|
//FIXME: keeps chasing goalEntity even when it's already reached it!
|
|
|
|
if ( NPCInfo->goalEntity
|
|
|
|
&& SandCreature_DistSqToGoal( qfalse ) >= MIN_ATTACK_DIST_SQ )
|
|
|
|
{
|
|
|
|
ucmd.buttons |= BUTTON_WALKING;
|
|
|
|
if ( SandCreature_Move() )
|
|
|
|
{
|
|
|
|
SandCreature_MoveEffect();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
NPC_ReachedGoal();
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
if ( UpdateGoal() )
|
|
|
|
{
|
|
|
|
ucmd.buttons |= BUTTON_WALKING;
|
|
|
|
//FIXME: Sand Creatures look silly using waypoints when they can go straight to the goal
|
|
|
|
if ( SandCreature_Move() )
|
|
|
|
{
|
|
|
|
SandCreature_MoveEffect();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
void SandCreature_PushEnts()
|
|
|
|
{
|
|
|
|
int numEnts;
|
|
|
|
gentity_t* radiusEnts[128];
|
|
|
|
const float radius = 70;
|
|
|
|
vec3_t mins, maxs;
|
|
|
|
vec3_t smackDir;
|
|
|
|
float smackDist;
|
|
|
|
|
|
|
|
for (int i = 0; i < 3; i++ )
|
|
|
|
{
|
|
|
|
mins[i] = NPC->currentOrigin[i] - radius;
|
|
|
|
maxs[i] = NPC->currentOrigin[i] + radius;
|
|
|
|
}
|
|
|
|
|
|
|
|
numEnts = gi.EntitiesInBox(mins, maxs, radiusEnts, 128);
|
|
|
|
for (int entIndex=0; entIndex<numEnts; entIndex++)
|
|
|
|
{
|
|
|
|
// Only Clients
|
|
|
|
//--------------
|
|
|
|
if (!radiusEnts[entIndex] || !radiusEnts[entIndex]->client || radiusEnts[entIndex]==NPC)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do The Vector Distance Test
|
|
|
|
//-----------------------------
|
|
|
|
VectorSubtract(radiusEnts[entIndex]->currentOrigin, NPC->currentOrigin, smackDir);
|
|
|
|
smackDist = VectorNormalize(smackDir);
|
|
|
|
if (smackDist<radius)
|
|
|
|
{
|
|
|
|
G_Throw(radiusEnts[entIndex], smackDir, 90);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void NPC_BSSandCreature_Default( void )
|
|
|
|
{
|
|
|
|
qboolean visible = qfalse;
|
|
|
|
|
|
|
|
//clear it every frame, will be set if we actually move this frame...
|
|
|
|
NPC->s.loopSound = 0;
|
|
|
|
|
|
|
|
if ( NPC->health > 0 && TIMER_Done( NPC, "breaching" ) )
|
|
|
|
{//go back to non-solid mode
|
|
|
|
if ( NPC->contents )
|
|
|
|
{
|
|
|
|
NPC->contents = 0;
|
|
|
|
}
|
|
|
|
if ( NPC->clipmask == MASK_NPCSOLID )
|
|
|
|
{
|
|
|
|
NPC->clipmask = CONTENTS_SOLID|CONTENTS_MONSTERCLIP;
|
|
|
|
}
|
|
|
|
if ( TIMER_Done( NPC, "speaking" ) )
|
|
|
|
{
|
|
|
|
G_SoundOnEnt( NPC, CHAN_VOICE, va( "sound/chars/sand_creature/voice%d.mp3", Q_irand( 1, 3 ) ) );
|
|
|
|
TIMER_Set( NPC, "speaking", Q_irand( 3000, 10000 ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{//still in breaching anim
|
|
|
|
visible = qtrue;
|
|
|
|
//FIXME: maybe push things up/away and maybe knock people down when doing this?
|
|
|
|
//FIXME: don't turn while breaching?
|
|
|
|
//FIXME: move faster while breaching?
|
|
|
|
//NOTE: shaking now done whenever he moves
|
|
|
|
}
|
|
|
|
|
|
|
|
//FIXME: when in start and end of attack/pain anims, need ground disturbance effect around him
|
|
|
|
// NOTENOTE: someone stubbed this code in, so I figured I'd use it. The timers are all weird, ie, magic numbers that sort of work,
|
|
|
|
// but maybe I'll try and figure out real values later if I have time.
|
|
|
|
if ( NPC->client->ps.legsAnim == BOTH_ATTACK1
|
|
|
|
|| NPC->client->ps.legsAnim == BOTH_ATTACK2 )
|
|
|
|
{//FIXME: get start and end frame numbers for this effect for each of these anims
|
|
|
|
vec3_t up={0,0,1};
|
|
|
|
vec3_t org;
|
|
|
|
VectorCopy( NPC->currentOrigin, org );
|
|
|
|
org[2] -= 40;
|
|
|
|
if ( NPC->client->ps.legsAnimTimer > 3700 )
|
|
|
|
{
|
|
|
|
// G_PlayEffect( G_EffectIndex( "env/sand_dive" ), NPC->currentOrigin, up );
|
|
|
|
G_PlayEffect( G_EffectIndex( "env/sand_spray" ), org, up );
|
|
|
|
}
|
|
|
|
else if ( NPC->client->ps.legsAnimTimer > 1600 && NPC->client->ps.legsAnimTimer < 1900 )
|
|
|
|
{
|
|
|
|
G_PlayEffect( G_EffectIndex( "env/sand_spray" ), org, up );
|
|
|
|
}
|
|
|
|
//G_PlayEffect( G_EffectIndex( "env/sand_attack_breach" ), org, up );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( !TIMER_Done( NPC, "pain" ) )
|
|
|
|
{
|
|
|
|
visible = qtrue;
|
|
|
|
}
|
|
|
|
else if ( !TIMER_Done( NPC, "attacking" ) )
|
|
|
|
{
|
|
|
|
visible = qtrue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( NPC->activator )
|
|
|
|
{//kill and remove the guy we ate
|
|
|
|
//FIXME: want to play ...? What was I going to say?
|
|
|
|
NPC->activator->health = 0;
|
|
|
|
GEntity_DieFunc( NPC->activator, NPC, NPC, 1000, MOD_MELEE, 0, HL_NONE );
|
|
|
|
if ( NPC->activator->s.number )
|
|
|
|
{
|
|
|
|
G_FreeEntity( NPC->activator );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{//can't remove the player, just make him invisible
|
|
|
|
NPC->client->ps.eFlags |= EF_NODRAW;
|
|
|
|
}
|
|
|
|
NPC->activator = NPC->enemy = NPCInfo->goalEntity = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( NPC->enemy )
|
|
|
|
{
|
|
|
|
SandCreature_Chase();
|
|
|
|
}
|
|
|
|
else if ( (level.time - NPCInfo->enemyLastSeenTime) < 5000 )//FIXME: should make this able to be variable
|
|
|
|
{//we were alerted recently, move towards there and look for footsteps, etc.
|
|
|
|
SandCreature_Hunt();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{//no alerts, sleep and wake up only by alerts
|
|
|
|
//FIXME: keeps chasing goalEntity even when it's already reached it!
|
|
|
|
SandCreature_Sleep();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NPC_UpdateAngles( qtrue, qtrue );
|
|
|
|
if ( !visible )
|
|
|
|
{
|
|
|
|
NPC->client->ps.eFlags |= EF_NODRAW;
|
|
|
|
NPC->s.eFlags |= EF_NODRAW;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
NPC->client->ps.eFlags &= ~EF_NODRAW;
|
|
|
|
NPC->s.eFlags &= ~EF_NODRAW;
|
|
|
|
|
|
|
|
SandCreature_PushEnts();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//FIXME: need pain behavior of sticking up through ground, writhing and screaming
|
2013-04-04 22:35:38 +00:00
|
|
|
//FIXME: need death anim like pain, but flopping aside and staying above ground...
|