codemp/game/NPC_AI_Droid.c

Go to the documentation of this file.
00001 #include "b_local.h"
00002 
00003 //static void R5D2_LookAround( void );
00004 float NPC_GetPainChance( gentity_t *self, int damage );
00005 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
00006 
00007 #define TURN_OFF   0x00000100
00008 
00009 //Local state enums
00010 enum
00011 {
00012         LSTATE_NONE = 0,
00013         LSTATE_BACKINGUP,
00014         LSTATE_SPINNING,
00015         LSTATE_PAIN,
00016         LSTATE_DROP
00017 };
00018 
00019 /*
00020 -------------------------
00021 R2D2_PartsMove
00022 -------------------------
00023 */
00024 void R2D2_PartsMove(void)
00025 {
00026         // Front 'eye' lense
00027         if ( TIMER_Done(NPC,"eyeDelay") )
00028         {
00029                 NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]);
00030 
00031                 NPC->pos1[0]+=Q_irand( -20, 20 );       // Roll 
00032                 NPC->pos1[1]=Q_irand( -20, 20 );        
00033                 NPC->pos1[2]=Q_irand( -20, 20 );        
00034 
00035                 /*
00036                 if (NPC->genericBone1)
00037                 {
00038                         gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone1, NPC->pos1, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); 
00039                 }
00040                 */
00041                 NPC_SetBoneAngles(NPC, "f_eye", NPC->pos1);
00042                 
00043 
00044                 TIMER_Set( NPC, "eyeDelay", Q_irand( 100, 1000 ) );
00045         }
00046 }
00047 
00048 /*
00049 -------------------------
00050 NPC_BSDroid_Idle
00051 -------------------------
00052 */
00053 void Droid_Idle( void )
00054 {
00055 //      VectorCopy( NPCInfo->investigateGoal, lookPos );
00056 
00057 //      NPC_FacePosition( lookPos );
00058 }
00059 
00060 /*
00061 -------------------------
00062 R2D2_TurnAnims
00063 -------------------------
00064 */
00065 void R2D2_TurnAnims ( void )
00066 {
00067         float turndelta;
00068         int             anim;
00069 
00070         turndelta = AngleDelta(NPC->r.currentAngles[YAW], NPCInfo->desiredYaw);
00071 
00072         if ((fabs(turndelta) > 20) && ((NPC->client->NPC_class == CLASS_R2D2) || (NPC->client->NPC_class == CLASS_R5D2)))
00073         {
00074                 anim = NPC->client->ps.legsAnim;
00075                 if (turndelta<0)
00076                 {
00077                         if (anim != BOTH_TURN_LEFT1)
00078                         {
00079                                 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURN_LEFT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00080                         }
00081                 }
00082                 else
00083                 {
00084                         if (anim != BOTH_TURN_RIGHT1)
00085                         {
00086                                 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURN_RIGHT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00087                         }
00088                 }
00089         }
00090         else
00091         {
00092                         NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00093         }
00094 
00095 }
00096 
00097 /*
00098 -------------------------
00099 Droid_Patrol
00100 -------------------------
00101 */
00102 void Droid_Patrol( void )
00103 {
00104 
00105         NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]);
00106 
00107         if ( NPC->client && NPC->client->NPC_class != CLASS_GONK )
00108         {
00109                 if (NPC->client->NPC_class != CLASS_R5D2)
00110                 { //he doesn't have an eye.
00111                         R2D2_PartsMove();               // Get his eye moving.
00112                 }
00113                 R2D2_TurnAnims();
00114         }
00115 
00116         //If we have somewhere to go, then do that
00117         if ( UpdateGoal() )
00118         {
00119                 ucmd.buttons |= BUTTON_WALKING;
00120                 NPC_MoveToGoal( qtrue );
00121 
00122                 if( NPC->client && NPC->client->NPC_class == CLASS_MOUSE )
00123                 {
00124                         NPCInfo->desiredYaw += sin(level.time*.5) * 25; // Weaves side to side a little
00125 
00126                         if (TIMER_Done(NPC,"patrolNoise"))
00127                         {
00128                                 G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/mouse/misc/mousego%d.wav", Q_irand(1, 3)) );
00129 
00130                                 TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
00131                         }
00132                 }
00133                 else if( NPC->client && NPC->client->NPC_class == CLASS_R2D2 )
00134                 {
00135                         if (TIMER_Done(NPC,"patrolNoise"))
00136                         {
00137                                 G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/r2d2/misc/r2d2talk0%d.wav",       Q_irand(1, 3)) );
00138 
00139                                 TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
00140                         }
00141                 }
00142                 else if( NPC->client && NPC->client->NPC_class == CLASS_R5D2 )
00143                 {
00144                         if (TIMER_Done(NPC,"patrolNoise"))
00145                         {
00146                                 G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/r5d2/misc/r5talk%d.wav", Q_irand(1, 4)) );
00147 
00148                                 TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
00149                         }
00150                 }
00151                 if( NPC->client && NPC->client->NPC_class == CLASS_GONK )
00152                 {
00153                         if (TIMER_Done(NPC,"patrolNoise"))
00154                         {
00155                                 G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/gonk/misc/gonktalk%d.wav", Q_irand(1, 2)) );
00156 
00157                                 TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
00158                         }
00159                 }
00160 //              else
00161 //              {
00162 //                      R5D2_LookAround();
00163 //              }
00164         }
00165 
00166         NPC_UpdateAngles( qtrue, qtrue );
00167 
00168 }
00169 
00170 /*
00171 -------------------------
00172 Droid_Run
00173 -------------------------
00174 */
00175 void Droid_Run( void )
00176 {
00177         R2D2_PartsMove();
00178 
00179         if ( NPCInfo->localState == LSTATE_BACKINGUP )
00180         {
00181                 ucmd.forwardmove = -127;
00182                 NPCInfo->desiredYaw += 5; 
00183 
00184                 NPCInfo->localState = LSTATE_NONE;      // So he doesn't constantly backup.
00185         }
00186         else 
00187         {
00188                 ucmd.forwardmove = 64;
00189                 //If we have somewhere to go, then do that
00190                 if ( UpdateGoal() )
00191                 {
00192                         if (NPC_MoveToGoal( qfalse ))
00193                         {
00194                                 NPCInfo->desiredYaw += sin(level.time*.5) * 5; // Weaves side to side a little
00195                         }
00196                 }
00197         }
00198 
00199         NPC_UpdateAngles( qtrue, qtrue );
00200 }
00201 
00202 /*
00203 -------------------------
00204 void Droid_Spin( void )
00205 -------------------------
00206 */
00207 void Droid_Spin( void )
00208 {
00209         vec3_t dir = {0,0,1};
00210 
00211         R2D2_TurnAnims();
00212 
00213                                                 
00214         // Head is gone, spin and spark
00215         if ( NPC->client->NPC_class == CLASS_R5D2 
00216                 || NPC->client->NPC_class == CLASS_R2D2 )
00217         {
00218                 // No head?
00219                 if (trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "head" )>0)
00220                 {
00221                         if (TIMER_Done(NPC,"smoke") && !TIMER_Done(NPC,"droidsmoketotal"))
00222                         {
00223                                 TIMER_Set( NPC, "smoke", 100);
00224                                 G_PlayEffectID( G_EffectIndex("volumetric/droid_smoke") , NPC->r.currentOrigin,dir);
00225                         }
00226 
00227                         if (TIMER_Done(NPC,"droidspark"))
00228                         {
00229                                 TIMER_Set( NPC, "droidspark", Q_irand(100,500));
00230                                 G_PlayEffectID( G_EffectIndex("sparks/spark"), NPC->r.currentOrigin,dir);
00231                         }
00232 
00233                         ucmd.forwardmove = Q_irand( -64, 64);
00234 
00235                         if (TIMER_Done(NPC,"roam"))
00236                         {       
00237                                 TIMER_Set( NPC, "roam", Q_irand( 250, 1000 ) );
00238                                 NPCInfo->desiredYaw = Q_irand( 0, 360 ); // Go in random directions
00239                         }
00240                 }
00241                 else
00242                 {
00243                         if (TIMER_Done(NPC,"roam"))
00244                         {
00245                                 NPCInfo->localState = LSTATE_NONE;
00246                         }
00247                         else
00248                         {
00249                                 NPCInfo->desiredYaw = AngleNormalize360(NPCInfo->desiredYaw + 40); // Spin around
00250                         }
00251                 }
00252         }
00253         else 
00254         {
00255                 if (TIMER_Done(NPC,"roam"))
00256                 {
00257                         NPCInfo->localState = LSTATE_NONE;
00258                 }
00259                 else
00260                 {
00261                         NPCInfo->desiredYaw = AngleNormalize360(NPCInfo->desiredYaw + 40); // Spin around
00262                 }
00263         }
00264 
00265         NPC_UpdateAngles( qtrue, qtrue );
00266 }
00267 
00268 /*
00269 -------------------------
00270 NPC_BSDroid_Pain
00271 -------------------------
00272 */
00273 void NPC_Droid_Pain(gentity_t *self, gentity_t *attacker, int damage)
00274 {
00275         gentity_t *other = attacker;
00276         int             anim;
00277         int             mod = gPainMOD;
00278         float   pain_chance;
00279 
00280         VectorCopy( self->NPC->lastPathAngles, self->s.angles );
00281 
00282         if ( self->client->NPC_class == CLASS_R5D2 )
00283         {
00284                 pain_chance = NPC_GetPainChance( self, damage );
00285 
00286                 // Put it in pain
00287                 if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance )       // Spin around in pain? Demp2 always does this
00288                 {
00289                         // Health is between 0-30 or was hit by a DEMP2 so pop his head
00290                         if ( !self->s.m_iVehicleNum
00291                                 && ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) )
00292                         {
00293                                 if (!(self->spawnflags & 2))    // Doesn't have to ALWAYSDIE
00294                                 {
00295                                         if ((self->NPC->localState != LSTATE_SPINNING) && 
00296                                                 (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" )))
00297                                         {
00298                                                 NPC_SetSurfaceOnOff( self, "head", TURN_OFF );
00299 
00300                                                 if ( self->client->ps.m_iVehicleNum )
00301                                                 {
00302                                                         vec3_t  up;
00303                                                         AngleVectors( self->r.currentAngles, NULL, NULL, up );
00304                                                         G_PlayEffectID( G_EffectIndex("chunks/r5d2head_veh"), self->r.currentOrigin, up );
00305                                                 }
00306                                                 else
00307                                                 {
00308                                                         G_PlayEffectID( G_EffectIndex("small_chunks") , self->r.currentOrigin, vec3_origin );
00309                                                         G_PlayEffectID( G_EffectIndex("chunks/r5d2head"), self->r.currentOrigin, vec3_origin );
00310                                                 }
00311 
00312                                                 //self->s.powerups |= ( 1 << PW_SHOCKED );
00313                                                 //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000;
00314                                                 self->client->ps.electrifyTime = level.time + 3000;
00315 
00316                                                 TIMER_Set( self, "droidsmoketotal", 5000);
00317                                                 TIMER_Set( self, "droidspark", 100);
00318                                                 self->NPC->localState = LSTATE_SPINNING;
00319                                         }
00320                                 }
00321                         }
00322                         // Just give him normal pain for a little while
00323                         else
00324                         {
00325                                 anim = self->client->ps.legsAnim;
00326 
00327                                 if ( anim == BOTH_STAND2 )      // On two legs?
00328                                 {
00329                                         anim = BOTH_PAIN1;
00330                                 }
00331                                 else                                            // On three legs
00332                                 {
00333                                         anim = BOTH_PAIN2;
00334                                 }
00335 
00336                                 NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00337 
00338                                 // Spin around in pain
00339                                 self->NPC->localState = LSTATE_SPINNING;
00340                                 TIMER_Set( self, "roam", Q_irand(1000,2000));
00341                         } 
00342                 }
00343         }
00344         else if (self->client->NPC_class == CLASS_MOUSE)
00345         {
00346                 if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT )
00347                 {
00348                         self->NPC->localState = LSTATE_SPINNING;
00349                         //self->s.powerups |= ( 1 << PW_SHOCKED );
00350                         //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000;
00351                         self->client->ps.electrifyTime = level.time + 3000;
00352                 }
00353                 else
00354                 {
00355                         self->NPC->localState = LSTATE_BACKINGUP;
00356                 }
00357 
00358                 self->NPC->scriptFlags &= ~SCF_LOOK_FOR_ENEMIES;
00359         }
00360         else if ((self->client->NPC_class == CLASS_R2D2))
00361         {
00362 
00363                 pain_chance = NPC_GetPainChance( self, damage );
00364 
00365                 if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance )       // Spin around in pain? Demp2 always does this
00366                 {
00367                         // Health is between 0-30 or was hit by a DEMP2 so pop his head
00368                         if ( !self->s.m_iVehicleNum
00369                                 && ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) )
00370                         {
00371                                 if (!(self->spawnflags & 2))    // Doesn't have to ALWAYSDIE
00372                                 {
00373                                         if ((self->NPC->localState != LSTATE_SPINNING) && 
00374                                                 (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" )))
00375                                         {
00376                                                 NPC_SetSurfaceOnOff( self, "head", TURN_OFF );
00377 
00378                                                 if ( self->client->ps.m_iVehicleNum )
00379                                                 {
00380                                                         vec3_t  up;
00381                                                         AngleVectors( self->r.currentAngles, NULL, NULL, up );
00382                                                         G_PlayEffectID( G_EffectIndex("chunks/r2d2head_veh"), self->r.currentOrigin, up );
00383                                                 }
00384                                                 else
00385                                                 {
00386                                                         G_PlayEffectID( G_EffectIndex("small_chunks") , self->r.currentOrigin, vec3_origin );
00387                                                         G_PlayEffectID( G_EffectIndex("chunks/r2d2head"), self->r.currentOrigin, vec3_origin );
00388                                                 }
00389 
00390                                                 //self->s.powerups |= ( 1 << PW_SHOCKED );
00391                                                 //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000;
00392                                                 self->client->ps.electrifyTime = level.time + 3000;
00393 
00394                                                 TIMER_Set( self, "droidsmoketotal", 5000);
00395                                                 TIMER_Set( self, "droidspark", 100);
00396                                                 self->NPC->localState = LSTATE_SPINNING;
00397                                         }
00398                                 }
00399                         }
00400                         // Just give him normal pain for a little while
00401                         else
00402                         {
00403                                 anim = self->client->ps.legsAnim;
00404 
00405                                 if ( anim == BOTH_STAND2 )      // On two legs?
00406                                 {
00407                                         anim = BOTH_PAIN1;
00408                                 }
00409                                 else                                            // On three legs
00410                                 {
00411                                         anim = BOTH_PAIN2;
00412                                 }
00413 
00414                                 NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00415 
00416                                 // Spin around in pain
00417                                 self->NPC->localState = LSTATE_SPINNING;
00418                                 TIMER_Set( self, "roam", Q_irand(1000,2000));
00419                         }
00420                 } 
00421         }
00422         else if ( self->client->NPC_class == CLASS_INTERROGATOR && ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) && other )
00423         {
00424                 vec3_t dir;
00425 
00426                 VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir );
00427                 VectorNormalize( dir );
00428 
00429                 VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity );
00430                 self->client->ps.velocity[2] -= 127;
00431         }
00432 
00433         NPC_Pain( self, attacker, damage);
00434 }
00435 
00436 
00437 /*
00438 -------------------------
00439 Droid_Pain
00440 -------------------------
00441 */
00442 void Droid_Pain(void)
00443 {
00444         if (TIMER_Done(NPC,"droidpain"))        //He's done jumping around
00445         {
00446                 NPCInfo->localState = LSTATE_NONE;
00447         }
00448 }
00449 
00450 /*
00451 -------------------------
00452 NPC_Mouse_Precache
00453 -------------------------
00454 */
00455 void NPC_Mouse_Precache( void )
00456 {
00457         int     i;
00458 
00459         for (i = 1; i < 4; i++)
00460         {
00461                 G_SoundIndex( va( "sound/chars/mouse/misc/mousego%d.wav", i ) );
00462         }
00463 
00464         G_EffectIndex( "env/small_explode" );
00465         G_SoundIndex( "sound/chars/mouse/misc/death1" );
00466         G_SoundIndex( "sound/chars/mouse/misc/mouse_lp" );
00467 }
00468 
00469 /*
00470 -------------------------
00471 NPC_R5D2_Precache
00472 -------------------------
00473 */
00474 void NPC_R5D2_Precache(void)
00475 {
00476         int i;
00477 
00478         for ( i = 1; i < 5; i++)
00479         {
00480                 G_SoundIndex( va( "sound/chars/r5d2/misc/r5talk%d.wav", i ) );
00481         }
00482         //G_SoundIndex( "sound/chars/r5d2/misc/falling1.wav" );
00483         G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); // ??
00484         G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp2.wav" );
00485         G_EffectIndex( "env/med_explode");
00486         G_EffectIndex( "volumetric/droid_smoke" );
00487         G_EffectIndex("sparks/spark");
00488         G_EffectIndex( "chunks/r5d2head");
00489         G_EffectIndex( "chunks/r5d2head_veh");
00490 }
00491 
00492 /*
00493 -------------------------
00494 NPC_R2D2_Precache
00495 -------------------------
00496 */
00497 void NPC_R2D2_Precache(void)
00498 {
00499         int i;
00500 
00501         for ( i = 1; i < 4; i++)
00502         {
00503                 G_SoundIndex( va( "sound/chars/r2d2/misc/r2d2talk0%d.wav", i ) );
00504         }
00505         //G_SoundIndex( "sound/chars/r2d2/misc/falling1.wav" );
00506         G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); // ??
00507         G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp.wav" );
00508         G_EffectIndex( "env/med_explode");
00509         G_EffectIndex( "volumetric/droid_smoke" );
00510         G_EffectIndex("sparks/spark");
00511         G_EffectIndex( "chunks/r2d2head");
00512         G_EffectIndex( "chunks/r2d2head_veh");
00513 }
00514 
00515 /*
00516 -------------------------
00517 NPC_Gonk_Precache
00518 -------------------------
00519 */
00520 void NPC_Gonk_Precache( void )
00521 {
00522         G_SoundIndex("sound/chars/gonk/misc/gonktalk1.wav");
00523         G_SoundIndex("sound/chars/gonk/misc/gonktalk2.wav");
00524 
00525         G_SoundIndex("sound/chars/gonk/misc/death1.wav");
00526         G_SoundIndex("sound/chars/gonk/misc/death2.wav");
00527         G_SoundIndex("sound/chars/gonk/misc/death3.wav");
00528 
00529         G_EffectIndex( "env/med_explode");
00530 }
00531 
00532 /*
00533 -------------------------
00534 NPC_Protocol_Precache
00535 -------------------------
00536 */
00537 void NPC_Protocol_Precache( void )
00538 {
00539         G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" );
00540         G_EffectIndex( "env/med_explode");
00541 }
00542 
00543 /*
00544 static void R5D2_OffsetLook( float offset, vec3_t out )
00545 {
00546         vec3_t  angles, forward, temp;
00547 
00548         GetAnglesForDirection( NPC->r.currentOrigin, NPCInfo->investigateGoal, angles );
00549         angles[YAW] += offset;
00550         AngleVectors( angles, forward, NULL, NULL );
00551         VectorMA( NPC->r.currentOrigin, 64, forward, out );
00552         
00553         CalcEntitySpot( NPC, SPOT_HEAD, temp );
00554         out[2] = temp[2];
00555 }
00556 */
00557 
00558 /*
00559 -------------------------
00560 R5D2_LookAround
00561 -------------------------
00562 */
00563 /*
00564 static void R5D2_LookAround( void )
00565 {
00566         vec3_t  lookPos;
00567         float   perc = (float) ( level.time - NPCInfo->pauseTime ) / (float) NPCInfo->investigateDebounceTime;
00568 
00569         //Keep looking at the spot
00570         if ( perc < 0.25 )
00571         {
00572                 VectorCopy( NPCInfo->investigateGoal, lookPos );
00573         }
00574         else if ( perc < 0.5f )         //Look up but straight ahead
00575         {
00576                 R5D2_OffsetLook( 0.0f, lookPos );
00577         }
00578         else if ( perc < 0.75f )        //Look right
00579         {
00580                 R5D2_OffsetLook( 45.0f, lookPos );
00581         }
00582         else    //Look left
00583         {
00584                 R5D2_OffsetLook( -45.0f, lookPos );
00585         }
00586 
00587         NPC_FacePosition( lookPos );
00588 }
00589 
00590 */
00591 
00592 /*
00593 -------------------------
00594 NPC_BSDroid_Default
00595 -------------------------
00596 */
00597 void NPC_BSDroid_Default( void )
00598 {
00599 
00600         if ( NPCInfo->localState == LSTATE_SPINNING )
00601         {
00602                 Droid_Spin();
00603         }
00604         else if ( NPCInfo->localState == LSTATE_PAIN )
00605         {
00606                 Droid_Pain();
00607         }
00608         else if ( NPCInfo->localState == LSTATE_DROP )
00609         {
00610                 NPC_UpdateAngles( qtrue, qtrue );
00611                 ucmd.upmove = crandom() * 64;
00612         }
00613         else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
00614         {
00615                 Droid_Patrol();
00616         }
00617         else
00618         {
00619                 Droid_Run();
00620         }
00621 }