123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912 |
- #include "g_headers.h"
- #include "b_local.h"
- //#include "g_nav.h"
- //#include "anims.h"
- //#include "wp_saber.h"
- extern qboolean PM_FlippingAnim( int anim );
- extern void NPC_BSST_Patrol( void );
- extern void RT_FlyStart( gentity_t *self );
- extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
- #define VELOCITY_DECAY 0.7f
- #define RT_FLYING_STRAFE_VEL 60
- #define RT_FLYING_STRAFE_DIS 200
- #define RT_FLYING_UPWARD_PUSH 150
- #define RT_FLYING_FORWARD_BASE_SPEED 50
- #define RT_FLYING_FORWARD_MULTIPLIER 10
- void RT_Precache( void )
- {
- G_SoundIndex( "sound/chars/boba/bf_blast-off.wav" );
- G_SoundIndex( "sound/chars/boba/bf_jetpack_lp.wav" );
- G_SoundIndex( "sound/chars/boba/bf_land.wav" );
- G_EffectIndex( "rockettrooper/flameNEW" );
- G_EffectIndex( "rockettrooper/light_cone" );//extern this? At least use a different one
- }
- extern void NPC_BehaviorSet_Stormtrooper( int bState );
- void RT_RunStormtrooperAI( void )
- {
- int bState;
- //Execute our bState
- if(NPCInfo->tempBehavior)
- {//Overrides normal behavior until cleared
- bState = NPCInfo->tempBehavior;
- }
- else
- {
- if(!NPCInfo->behaviorState)
- NPCInfo->behaviorState = NPCInfo->defaultBehavior;
- bState = NPCInfo->behaviorState;
- }
- NPC_BehaviorSet_Stormtrooper( bState );
- }
- void RT_FireDecide( void )
- {
- qboolean enemyLOS = qfalse;
- qboolean enemyCS = qfalse;
- qboolean enemyInFOV = qfalse;
- //qboolean move = qtrue;
- qboolean faceEnemy = qfalse;
- qboolean shoot = qfalse;
- qboolean hitAlly = qfalse;
- vec3_t impactPos;
- float enemyDist;
- if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE
- && NPC->client->ps.forceJumpZStart
- && !PM_FlippingAnim( NPC->client->ps.legsAnim )
- && !Q_irand( 0, 10 ) )
- {//take off
- RT_FlyStart( NPC );
- }
- if ( !NPC->enemy )
- {
- return;
- }
- VectorClear( impactPos );
- enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
- vec3_t enemyDir, shootDir;
- VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, enemyDir );
- VectorNormalize( enemyDir );
- AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL );
- float dot = DotProduct( enemyDir, shootDir );
- if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 )
- {//enemy is in front of me or they're very close and not behind me
- enemyInFOV = qtrue;
- }
- if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128
- {//enemy within 128
- if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) &&
- (NPCInfo->scriptFlags & SCF_ALT_FIRE) )
- {//shooting an explosive, but enemy too close, switch to primary fire
- NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
- //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not...
- }
- }
- //can we see our target?
- if ( TIMER_Done( NPC, "nextAttackDelay" ) && TIMER_Done( NPC, "flameTime" ) )
- {
- if ( NPC_ClearLOS( NPC->enemy ) )
- {
- NPCInfo->enemyLastSeenTime = level.time;
- enemyLOS = qtrue;
- if ( NPC->client->ps.weapon == WP_NONE )
- {
- enemyCS = qfalse;//not true, but should stop us from firing
- }
- else
- {//can we shoot our target?
- if ( (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER
- || (NPC->client->ps.weapon == WP_CONCUSSION && !(NPCInfo->scriptFlags&SCF_ALT_FIRE))
- || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )//128*128
- {
- enemyCS = qfalse;//not true, but should stop us from firing
- hitAlly = qtrue;//us!
- //FIXME: if too close, run away!
- }
- else if ( enemyInFOV )
- {//if enemy is FOV, go ahead and check for shooting
- int hit = NPC_ShotEntity( NPC->enemy, impactPos );
- gentity_t *hitEnt = &g_entities[hit];
- if ( hit == NPC->enemy->s.number
- || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam )
- || ( hitEnt && hitEnt->takedamage && ((hitEnt->svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) )
- {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway
- enemyCS = qtrue;
- //NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy
- VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation );
- }
- else
- {//Hmm, have to get around this bastard
- //NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy
- if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam )
- {//would hit an ally, don't fire!!!
- hitAlly = qtrue;
- }
- else
- {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire
- }
- }
- }
- else
- {
- enemyCS = qfalse;//not true, but should stop us from firing
- }
- }
- }
- else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) )
- {
- NPCInfo->enemyLastSeenTime = level.time;
- faceEnemy = qtrue;
- //NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy
- }
- if ( NPC->client->ps.weapon == WP_NONE )
- {
- faceEnemy = qfalse;
- shoot = qfalse;
- }
- else
- {
- if ( enemyLOS )
- {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot?
- faceEnemy = qtrue;
- }
- if ( enemyCS )
- {
- shoot = qtrue;
- }
- }
- if ( !enemyCS )
- {//if have a clear shot, always try
- //See if we should continue to fire on their last position
- //!TIMER_Done( NPC, "stick" ) ||
- if ( !hitAlly //we're not going to hit an ally
- && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS?
- && NPCInfo->enemyLastSeenTime > 0 )//we've seen the enemy
- {
- if ( level.time - NPCInfo->enemyLastSeenTime < 10000 )//we have seem the enemy in the last 10 seconds
- {
- if ( !Q_irand( 0, 10 ) )
- {
- //Fire on the last known position
- vec3_t muzzle, dir, angles;
- qboolean tooClose = qfalse;
- qboolean tooFar = qfalse;
- CalcEntitySpot( NPC, SPOT_HEAD, muzzle );
- if ( VectorCompare( impactPos, vec3_origin ) )
- {//never checked ShotEntity this frame, so must do a trace...
- trace_t tr;
- //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2};
- vec3_t forward, end;
- AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL );
- VectorMA( muzzle, 8192, forward, end );
- gi.trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT );
- VectorCopy( tr.endpos, impactPos );
- }
- //see if impact would be too close to me
- float distThreshold = 16384/*128*128*/;//default
- switch ( NPC->s.weapon )
- {
- case WP_ROCKET_LAUNCHER:
- case WP_FLECHETTE:
- case WP_THERMAL:
- case WP_TRIP_MINE:
- case WP_DET_PACK:
- distThreshold = 65536/*256*256*/;
- break;
- case WP_REPEATER:
- if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
- {
- distThreshold = 65536/*256*256*/;
- }
- break;
- case WP_CONCUSSION:
- if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
- {
- distThreshold = 65536/*256*256*/;
- }
- break;
- default:
- break;
- }
- float dist = DistanceSquared( impactPos, muzzle );
- if ( dist < distThreshold )
- {//impact would be too close to me
- tooClose = qtrue;
- }
- else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 ||
- (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 ))
- {//we've haven't seen them in the last 5 seconds
- //see if it's too far from where he is
- distThreshold = 65536/*256*256*/;//default
- switch ( NPC->s.weapon )
- {
- case WP_ROCKET_LAUNCHER:
- case WP_FLECHETTE:
- case WP_THERMAL:
- case WP_TRIP_MINE:
- case WP_DET_PACK:
- distThreshold = 262144/*512*512*/;
- break;
- case WP_REPEATER:
- if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
- {
- distThreshold = 262144/*512*512*/;
- }
- break;
- case WP_CONCUSSION:
- if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
- {
- distThreshold = 262144/*512*512*/;
- }
- break;
- default:
- break;
- }
- dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation );
- if ( dist > distThreshold )
- {//impact would be too far from enemy
- tooFar = qtrue;
- }
- }
- if ( !tooClose && !tooFar )
- {//okay too shoot at last pos
- VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir );
- VectorNormalize( dir );
- vectoangles( dir, angles );
- NPCInfo->desiredYaw = angles[YAW];
- NPCInfo->desiredPitch = angles[PITCH];
- shoot = qtrue;
- faceEnemy = qfalse;
- }
- }
- }
- }
- }
- //FIXME: don't shoot right away!
- if ( NPC->client->fireDelay )
- {
- if ( NPC->s.weapon == WP_ROCKET_LAUNCHER
- || (NPC->s.weapon == WP_CONCUSSION&&!(NPCInfo->scriptFlags&SCF_ALT_FIRE)) )
- {
- if ( !enemyLOS || !enemyCS )
- {//cancel it
- NPC->client->fireDelay = 0;
- }
- else
- {//delay our next attempt
- TIMER_Set( NPC, "nextAttackDelay", Q_irand( 1000, 3000 ) );//FIXME: base on g_spskill
- }
- }
- }
- else if ( shoot )
- {//try to shoot if it's time
- if ( TIMER_Done( NPC, "nextAttackDelay" ) )
- {
- if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here
- {
- WeaponThink( qtrue );
- }
- //NASTY
- int altChance = 6;//FIXME: base on g_spskill
- if ( NPC->s.weapon == WP_ROCKET_LAUNCHER )
- {
- if ( (ucmd.buttons&BUTTON_ATTACK)
- && !Q_irand( 0, altChance ) )
- {//every now and then, shoot a homing rocket
- ucmd.buttons &= ~BUTTON_ATTACK;
- ucmd.buttons |= BUTTON_ALT_ATTACK;
- NPC->client->fireDelay = Q_irand( 1000, 3000 );//FIXME: base on g_spskill
- }
- }
- else if ( NPC->s.weapon == WP_CONCUSSION )
- {
- if ( (ucmd.buttons&BUTTON_ATTACK)
- && Q_irand( 0, altChance*5 ) )
- {//fire the beam shot
- ucmd.buttons &= ~BUTTON_ATTACK;
- ucmd.buttons |= BUTTON_ALT_ATTACK;
- TIMER_Set( NPC, "nextAttackDelay", Q_irand( 1500, 2500 ) );//FIXME: base on g_spskill
- }
- else
- {//fire the rocket-like shot
- TIMER_Set( NPC, "nextAttackDelay", Q_irand( 3000, 5000 ) );//FIXME: base on g_spskill
- }
- }
- }
- }
- }
- }
- //=====================================================================================
- //FLYING behavior
- //=====================================================================================
- qboolean RT_Flying( gentity_t *self )
- {
- return ((qboolean)(self->client->moveType==MT_FLYSWIM));
- }
- void RT_FlyStart( gentity_t *self )
- {//switch to seeker AI for a while
- if ( TIMER_Done( self, "jetRecharge" )
- && !RT_Flying( self ) )
- {
- self->client->ps.gravity = 0;
- self->svFlags |= SVF_CUSTOM_GRAVITY;
- self->client->moveType = MT_FLYSWIM;
- //Inform NPC_HandleAIFlags we want to fly
- self->NPC->aiFlags |= NPCAI_FLY;
- self->lastInAirTime = level.time;
-
- //start jet effect
- self->client->jetPackTime = Q3_INFINITE;
- if ( self->genericBolt1 != -1 )
- {
- G_PlayEffect( G_EffectIndex( "rockettrooper/flameNEW" ), self->playerModel, self->genericBolt1, self->s.number, self->currentOrigin, qtrue, qtrue );
- }
- if ( self->genericBolt2 != -1 )
- {
- G_PlayEffect( G_EffectIndex( "rockettrooper/flameNEW" ), self->playerModel, self->genericBolt2, self->s.number, self->currentOrigin, qtrue, qtrue );
- }
- //take-off sound
- G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/boba/bf_blast-off.wav" );
- //jet loop sound
- self->s.loopSound = G_SoundIndex( "sound/chars/boba/bf_jetpack_lp.wav" );
- if ( self->NPC )
- {
- self->count = Q3_INFINITE; // SEEKER shot ammo count
- }
- }
- }
- void RT_FlyStop( gentity_t *self )
- {
- self->client->ps.gravity = g_gravity->value;
- self->svFlags &= ~SVF_CUSTOM_GRAVITY;
- self->client->moveType = MT_RUNJUMP;
- //Stop the effect
- self->client->jetPackTime = 0;
- if ( self->genericBolt1 != -1 )
- {
- G_StopEffect("rockettrooper/flameNEW", self->playerModel, self->genericBolt1, self->s.number );
- }
- if ( self->genericBolt2 != -1 )
- {
- G_StopEffect("rockettrooper/flameNEW", self->playerModel, self->genericBolt2, self->s.number );
- }
- //stop jet loop sound
- self->s.loopSound = 0;
- G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/boba/bf_land.wav" );
- if ( self->NPC )
- {
- self->count = 0; // SEEKER shot ammo count
- TIMER_Set( self, "jetRecharge", Q_irand( 1000, 5000 ) );
- TIMER_Set( self, "jumpChaseDebounce", Q_irand( 500, 2000 ) );
- }
- }
- void RT_JetPackEffect( int duration )
- {
- if ( NPC->genericBolt1 != -1 )
- {
- G_PlayEffect( G_EffectIndex( "rockettrooper/flameNEW" ), NPC->playerModel, NPC->genericBolt1, NPC->s.number, NPC->currentOrigin, duration, qtrue );
- }
- if ( NPC->genericBolt2 != -1 )
- {
- G_PlayEffect( G_EffectIndex( "rockettrooper/flameNEW" ), NPC->playerModel, NPC->genericBolt2, NPC->s.number, NPC->currentOrigin, duration, qtrue );
- }
- //take-off sound
- G_SoundOnEnt( NPC, CHAN_ITEM, "sound/chars/boba/bf_blast-off.wav" );
- }
- void RT_Flying_ApplyFriction( float frictionScale )
- {
- if ( NPC->client->ps.velocity[0] )
- {
- NPC->client->ps.velocity[0] *= VELOCITY_DECAY;///frictionScale;
- if ( fabs( NPC->client->ps.velocity[0] ) < 1 )
- {
- NPC->client->ps.velocity[0] = 0;
- }
- }
- if ( NPC->client->ps.velocity[1] )
- {
- NPC->client->ps.velocity[1] *= VELOCITY_DECAY;///frictionScale;
- if ( fabs( NPC->client->ps.velocity[1] ) < 1 )
- {
- NPC->client->ps.velocity[1] = 0;
- }
- }
- }
- void RT_Flying_MaintainHeight( void )
- {
- float dif = 0;
- // Update our angles regardless
- NPC_UpdateAngles( qtrue, qtrue );
- if ( NPC->forcePushTime > level.time )
- {//if being pushed, we don't have control over our movement
- return;
- }
- if ( (NPC->client->ps.pm_flags&PMF_TIME_KNOCKBACK) )
- {//don't slow down for a bit
- if ( NPC->client->ps.pm_time > 0 )
- {
- VectorScale( NPC->client->ps.velocity, 0.9f, NPC->client->ps.velocity );
- return;
- }
- }
- /*
- if ( (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) )
- {
- RT_Flying_ApplyFriction( 3.0f );
- return;
- }
- */
- // If we have an enemy, we should try to hover at or a little below enemy eye level
- if ( NPC->enemy
- && (!Q3_TaskIDPending( NPC, TID_MOVE_NAV ) || !NPCInfo->goalEntity ) )
- {
- if (TIMER_Done( NPC, "heightChange" ))
- {
- TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 ));
-
- float enemyZHeight = NPC->enemy->currentOrigin[2];
- if ( NPC->enemy->client
- && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE
- && (NPC->enemy->client->ps.forcePowersActive&(1<<FP_LEVITATION)) )
- {//so we don't go up when they force jump up at us
- enemyZHeight = NPC->enemy->client->ps.forceJumpZStart;
- }
- // Find the height difference
- dif = (enemyZHeight + Q_flrand( NPC->enemy->maxs[2]/2, NPC->enemy->maxs[2]+8 )) - NPC->currentOrigin[2];
- float difFactor = 10.0f;
- // cap to prevent dramatic height shifts
- if ( fabs( dif ) > 2*difFactor )
- {
- if ( fabs( dif ) > 20*difFactor )
- {
- dif = ( dif < 0 ? -20*difFactor : 20*difFactor );
- }
- NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2;
- }
- NPC->client->ps.velocity[2] *= Q_flrand( 0.85f, 1.25f );
- }
- else
- {//don't get too far away from height of enemy...
- float enemyZHeight = NPC->enemy->currentOrigin[2];
- if ( NPC->enemy->client
- && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE
- && (NPC->enemy->client->ps.forcePowersActive&(1<<FP_LEVITATION)) )
- {//so we don't go up when they force jump up at us
- enemyZHeight = NPC->enemy->client->ps.forceJumpZStart;
- }
- dif = NPC->currentOrigin[2] - (enemyZHeight+64);
- float maxHeight = 200;
- float hDist = DistanceHorizontal( NPC->enemy->currentOrigin, NPC->currentOrigin );
- if ( hDist < 512 )
- {
- maxHeight *= hDist/512;
- }
- if ( dif > maxHeight )
- {
- if ( NPC->client->ps.velocity[2] > 0 )//FIXME: or: we can't see him anymore
- {//slow down
- if ( NPC->client->ps.velocity[2] )
- {
- NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
- if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
- {
- NPC->client->ps.velocity[2] = 0;
- }
- }
- }
- else
- {//start coming back down
- NPC->client->ps.velocity[2] -= 4;
- }
- }
- else if ( dif < -200 && NPC->client->ps.velocity[2] < 0 )//we're way below him
- {
- if ( NPC->client->ps.velocity[2] < 0 )//FIXME: or: we can't see him anymore
- {//slow down
- if ( NPC->client->ps.velocity[2] )
- {
- NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
- if ( fabs( NPC->client->ps.velocity[2] ) > -2 )
- {
- NPC->client->ps.velocity[2] = 0;
- }
- }
- }
- else
- {//start going back up
- NPC->client->ps.velocity[2] += 4;
- }
- }
- }
- }
- else
- {
- gentity_t *goal = NULL;
- if ( NPCInfo->goalEntity ) // Is there a goal?
- {
- goal = NPCInfo->goalEntity;
- }
- else
- {
- goal = NPCInfo->lastGoalEntity;
- }
- if ( goal )
- {
- dif = goal->currentOrigin[2] - NPC->currentOrigin[2];
- }
- else if ( VectorCompare( NPC->pos1, vec3_origin ) )
- {//have a starting position as a reference point
- dif = NPC->pos1[2] - NPC->currentOrigin[2];
- }
- if ( fabs( dif ) > 24 )
- {
- ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 );
- }
- else
- {
- if ( NPC->client->ps.velocity[2] )
- {
- NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
- if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
- {
- NPC->client->ps.velocity[2] = 0;
- }
- }
- }
- }
- // Apply friction
- RT_Flying_ApplyFriction( 1.0f );
- }
- void RT_Flying_Strafe( void )
- {
- int side;
- vec3_t end, right, dir;
- trace_t tr;
- if ( random() > 0.7f
- || !NPC->enemy
- || !NPC->enemy->client )
- {
- // Do a regular style strafe
- AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL );
- // Pick a random strafe direction, then check to see if doing a strafe would be
- // reasonably valid
- side = ( rand() & 1 ) ? -1 : 1;
- VectorMA( NPC->currentOrigin, RT_FLYING_STRAFE_DIS * side, right, end );
- gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
- // Close enough
- if ( tr.fraction > 0.9f )
- {
- float vel = RT_FLYING_STRAFE_VEL+Q_flrand(-20,20);
- VectorMA( NPC->client->ps.velocity, vel*side, right, NPC->client->ps.velocity );
- if ( !Q_irand( 0, 3 ) )
- {
- // Add a slight upward push
- float upPush = RT_FLYING_UPWARD_PUSH;
- if ( NPC->client->ps.velocity[2] < 300 )
- {
- if ( NPC->client->ps.velocity[2] < 300+upPush )
- {
- NPC->client->ps.velocity[2] += upPush;
- }
- else
- {
- NPC->client->ps.velocity[2] = 300;
- }
- }
- }
- // NPCInfo->standTime = level.time + 1000 + random() * 500; // Original
- // NPCInfo->standTime = level.time + 2000 + random() * 500; // Revision 1
- NPCInfo->standTime = level.time + 1500 + random() * 500; // Revision 2
- }
- }
- else
- {
- // Do a strafe to try and keep on the side of their enemy
- AngleVectors( NPC->enemy->client->renderInfo.eyeAngles, dir, right, NULL );
- // Pick a random side
- side = ( rand() & 1 ) ? -1 : 1;
- float stDis = RT_FLYING_STRAFE_DIS*2.0f;
- VectorMA( NPC->enemy->currentOrigin, stDis * side, right, end );
- // then add a very small bit of random in front of/behind the player action
- VectorMA( end, crandom() * 25, dir, end );
- gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
- // Close enough
- if ( tr.fraction > 0.9f )
- {
- float vel = (RT_FLYING_STRAFE_VEL*4)+Q_flrand(-20,20);
- VectorSubtract( tr.endpos, NPC->currentOrigin, dir );
- dir[2] *= 0.25; // do less upward change
- float dis = VectorNormalize( dir );
- if ( dis > vel )
- {
- dis = vel;
- }
- // Try to move the desired enemy side
- VectorMA( NPC->client->ps.velocity, dis, dir, NPC->client->ps.velocity );
- if ( !Q_irand( 0, 3 ) )
- {
- float upPush = RT_FLYING_UPWARD_PUSH;
- // Add a slight upward push
- if ( NPC->client->ps.velocity[2] < 300 )
- {
- if ( NPC->client->ps.velocity[2] < 300+upPush )
- {
- NPC->client->ps.velocity[2] += upPush;
- }
- else
- {
- NPC->client->ps.velocity[2] = 300;
- }
- }
- else if ( NPC->client->ps.velocity[2] > 300 )
- {
- NPC->client->ps.velocity[2] = 300;
- }
- }
- // NPCInfo->standTime = level.time + 2500 + random() * 500; // Original
- // NPCInfo->standTime = level.time + 5000 + random() * 500; // Revision 1
- NPCInfo->standTime = level.time + 3500 + random() * 500; // Revision 2
- }
- }
- }
- void RT_Flying_Hunt( qboolean visible, qboolean advance )
- {
- float distance, speed;
- vec3_t forward;
- if ( NPC->forcePushTime >= level.time )
- //|| (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) )
- {//if being pushed, we don't have control over our movement
- NPC->delay = 0;
- return;
- }
- NPC_FaceEnemy( qtrue );
- // If we're not supposed to stand still, pursue the player
- if ( NPCInfo->standTime < level.time )
- {
- // Only strafe when we can see the player
- if ( visible )
- {
- NPC->delay = 0;
- RT_Flying_Strafe();
- return;
- }
- }
- // If we don't want to advance, stop here
- if ( advance )
- {
- // Only try and navigate if the player is visible
- if ( visible == qfalse )
- {
- // Move towards our goal
- NPCInfo->goalEntity = NPC->enemy;
- NPCInfo->goalRadius = 24;
- NPC->delay = 0;
- NPC_MoveToGoal(qtrue);
- return;
- }
- }
- //else move straight at/away from him
- VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward );
- forward[2] *= 0.1f;
- distance = VectorNormalize( forward );
- speed = RT_FLYING_FORWARD_BASE_SPEED + RT_FLYING_FORWARD_MULTIPLIER * g_spskill->integer;
- if ( advance && distance < Q_flrand( 256, 3096 ) )
- {
- NPC->delay = 0;
- VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity );
- }
- else if ( distance < Q_flrand( 0, 128 ) )
- {
- if ( NPC->health <= 50 )
- {//always back off
- NPC->delay = 0;
- }
- else if ( !TIMER_Done( NPC, "backoffTime" ) )
- {//still backing off from end of last delay
- NPC->delay = 0;
- }
- else if ( !NPC->delay )
- {//start a new delay
- NPC->delay = Q_irand( 0, 10+(20*(2-g_spskill->integer)) );
- }
- else
- {//continue the current delay
- NPC->delay--;
- }
- if ( !NPC->delay )
- {//delay done, now back off for a few seconds!
- TIMER_Set( NPC, "backoffTime", Q_irand( 2000, 5000 ) );
- VectorMA( NPC->client->ps.velocity, speed*-2, forward, NPC->client->ps.velocity );
- }
- }
- else
- {
- NPC->delay = 0;
- }
- }
- void RT_Flying_Ranged( qboolean visible, qboolean advance )
- {
- if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
- {
- RT_Flying_Hunt( visible, advance );
- }
- }
- void RT_Flying_Attack( void )
- {
- // Always keep a good height off the ground
- RT_Flying_MaintainHeight();
- // Rate our distance to the target, and our visibilty
- float distance = DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
- qboolean visible = NPC_ClearLOS( NPC->enemy );
- qboolean advance = (qboolean)(distance>(256.0f*256.0f));
- // If we cannot see our target, move to see it
- if ( visible == qfalse )
- {
- if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
- {
- RT_Flying_Hunt( visible, advance );
- return;
- }
- }
- RT_Flying_Ranged( visible, advance );
- }
- void RT_Flying_Think( void )
- {
- if ( Q3_TaskIDPending( NPC, TID_MOVE_NAV )
- && UpdateGoal() )
- {//being scripted to go to a certain spot, don't maintain height
- if ( NPC_MoveToGoal( qtrue ) )
- {//we could macro-nav to our goal
- if ( NPC->enemy && NPC->enemy->health && NPC->enemy->inuse )
- {
- NPC_FaceEnemy( qtrue );
- RT_FireDecide();
- }
- }
- else
- {//frick, no where to nav to, keep us in the air!
- RT_Flying_MaintainHeight();
- }
- return;
- }
- if ( NPC->random == 0.0f )
- {
- // used to offset seekers around a circle so they don't occupy the same spot. This is not a fool-proof method.
- NPC->random = random() * 6.3f; // roughly 2pi
- }
- if ( NPC->enemy && NPC->enemy->health && NPC->enemy->inuse )
- {
- RT_Flying_Attack();
- RT_FireDecide();
- return;
- }
- else
- {
- RT_Flying_MaintainHeight();
- RT_RunStormtrooperAI();
- return;
- }
- }
- //=====================================================================================
- //ON GROUND WITH ENEMY behavior
- //=====================================================================================
- //=====================================================================================
- //DEFAULT behavior
- //=====================================================================================
- extern void RT_CheckJump( void );
- void NPC_BSRT_Default( void )
- {
- //FIXME: custom pain and death funcs:
- //pain3 is in air
- //die in air is both_falldeath1
- //attack1 is on ground, attack2 is in air
- //FIXME: this doesn't belong here
- if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE )
- {
- if ( NPCInfo->rank >= RANK_LT )//&& !Q_irand( 0, 50 ) )
- {//officers always stay in the air
- NPC->client->ps.velocity[2] = Q_irand( 50, 125 );
- NPC->NPC->aiFlags |= NPCAI_FLY; //fixme also, Inform NPC_HandleAIFlags we want to fly
- }
- }
- if ( RT_Flying( NPC ) )
- {//FIXME: only officers need do this, right?
- RT_Flying_Think();
- }
- else if ( NPC->enemy != NULL )
- {//rocketrooper on ground with enemy
- UpdateGoal();
- RT_RunStormtrooperAI();
- RT_CheckJump();
- //NPC_BSST_Default();//FIXME: add missile avoidance
- //RT_Hunt();//NPC_BehaviorSet_Jedi( bState );
- }
- else
- {//shouldn't have gotten in here
- RT_RunStormtrooperAI();
- //NPC_BSST_Patrol();
- }
- }
|