Initial commit.
This commit is contained in:
818
code/game/AI_SandCreature.cpp
Normal file
818
code/game/AI_SandCreature.cpp
Normal file
@@ -0,0 +1,818 @@
|
||||
// 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...
|
||||
Reference in New Issue
Block a user