123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818 |
- // 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...
|