jediacademy/code/game/AI_SandCreature.cpp

818 lines
23 KiB
C++

// 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
//FIXME: need death anim like pain, but flopping aside and staying above ground...