00001
00002 #include "b_local.h"
00003 #include "g_nav.h"
00004
00005 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
00006 extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
00007 extern qboolean NPC_CheckLookTarget( gentity_t *self );
00008 extern void NPC_ClearLookTarget( gentity_t *self );
00009 extern void NPC_Jedi_RateNewEnemy( gentity_t *self, gentity_t *enemy );
00010 extern int NAV_FindClosestWaypointForPoint2( vec3_t point );
00011 extern int NAV_GetNearestNode( gentity_t *self, int lastNode );
00012 extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum );
00013 extern qboolean PM_DroidMelee( int npc_class );
00014
00015 void ChangeWeapon( gentity_t *ent, int newWeapon );
00016
00017 void G_ClearEnemy (gentity_t *self)
00018 {
00019 NPC_CheckLookTarget( self );
00020
00021 if ( self->enemy )
00022 {
00023 if( self->client && self->client->renderInfo.lookTarget == self->enemy->s.number )
00024 {
00025 NPC_ClearLookTarget( self );
00026 }
00027
00028 if ( self->NPC && self->enemy == self->NPC->goalEntity )
00029 {
00030 self->NPC->goalEntity = NULL;
00031 }
00032
00033 }
00034
00035 self->enemy = NULL;
00036 }
00037
00038
00039
00040
00041
00042
00043
00044 #define ANGER_ALERT_RADIUS 512
00045 #define ANGER_ALERT_SOUND_RADIUS 256
00046
00047 void G_AngerAlert( gentity_t *self )
00048 {
00049 if ( self && self->NPC && (self->NPC->scriptFlags&SCF_NO_GROUPS) )
00050 {
00051 return;
00052 }
00053 if ( !TIMER_Done( self, "interrogating" ) )
00054 {
00055 return;
00056 }
00057
00058 G_AlertTeam( self, self->enemy, ANGER_ALERT_RADIUS, ANGER_ALERT_SOUND_RADIUS );
00059 }
00060
00061
00062
00063
00064
00065
00066
00067 qboolean G_TeamEnemy( gentity_t *self )
00068 {
00069 int i;
00070 gentity_t *ent;
00071
00072 if ( !self->client || self->client->playerTeam == TEAM_FREE )
00073 {
00074 return qfalse;
00075 }
00076 if ( self && self->NPC && (self->NPC->scriptFlags&SCF_NO_GROUPS) )
00077 {
00078 return qfalse;
00079 }
00080
00081 for( i = 1; i < level.num_entities; i++ )
00082 {
00083 ent = &g_entities[i];
00084
00085 if ( ent == self )
00086 {
00087 continue;
00088 }
00089
00090 if ( ent->health <= 0 )
00091 {
00092 continue;
00093 }
00094
00095 if ( !ent->client )
00096 {
00097 continue;
00098 }
00099
00100 if ( ent->client->playerTeam != self->client->playerTeam )
00101 {
00102 continue;
00103 }
00104
00105 if ( ent->enemy )
00106 {
00107 if ( !ent->enemy->client || ent->enemy->client->playerTeam != self->client->playerTeam )
00108 {
00109 return qtrue;
00110 }
00111 }
00112 }
00113
00114 return qfalse;
00115 }
00116
00117 void G_AttackDelay( gentity_t *self, gentity_t *enemy )
00118 {
00119 if ( enemy && self->client && self->NPC )
00120 {
00121 vec3_t fwd, dir;
00122 int attDelay;
00123
00124 VectorSubtract( self->client->renderInfo.eyePoint, enemy->r.currentOrigin, dir );
00125 VectorNormalize( dir );
00126 AngleVectors( self->client->renderInfo.eyeAngles, fwd, NULL, NULL );
00127
00128
00129 attDelay = (4-g_spskill.integer)*500;
00130 if ( self->client->playerTeam == NPCTEAM_PLAYER )
00131 {
00132 attDelay = 2000-attDelay;
00133 }
00134 attDelay += floor( (DotProduct( fwd, dir )+1.0f) * 2000.0f );
00135
00136
00137
00138
00139
00140 switch ( self->client->NPC_class )
00141 {
00142 case CLASS_IMPERIAL:
00143 attDelay += Q_irand( 500, 1500 );
00144 break;
00145 case CLASS_STORMTROOPER:
00146 if ( self->NPC->rank >= RANK_LT )
00147 {
00148 attDelay -= Q_irand( 500, 1500 );
00149 }
00150 else
00151 {
00152 attDelay -= Q_irand( 0, 1000 );
00153 }
00154 break;
00155 case CLASS_SWAMPTROOPER:
00156 attDelay -= Q_irand( 1000, 2000 );
00157 break;
00158 case CLASS_IMPWORKER:
00159 attDelay += Q_irand( 1000, 2500 );
00160 break;
00161 case CLASS_TRANDOSHAN:
00162 attDelay -= Q_irand( 500, 1500 );
00163 break;
00164 case CLASS_JAN:
00165 case CLASS_LANDO:
00166 case CLASS_PRISONER:
00167 case CLASS_REBEL:
00168 attDelay -= Q_irand( 500, 1500 );
00169 break;
00170 case CLASS_GALAKMECH:
00171 case CLASS_ATST:
00172 attDelay -= Q_irand( 1000, 2000 );
00173 break;
00174 case CLASS_REELO:
00175 case CLASS_UGNAUGHT:
00176 case CLASS_JAWA:
00177 return;
00178 break;
00179 case CLASS_MINEMONSTER:
00180 case CLASS_MURJJ:
00181 return;
00182 break;
00183 case CLASS_INTERROGATOR:
00184 case CLASS_PROBE:
00185 case CLASS_MARK1:
00186 case CLASS_MARK2:
00187 case CLASS_SENTRY:
00188 return;
00189 break;
00190 case CLASS_REMOTE:
00191 case CLASS_SEEKER:
00192 return;
00193 break;
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207 }
00208
00209 switch ( self->s.weapon )
00210 {
00211 case WP_NONE:
00212 case WP_SABER:
00213 return;
00214 break;
00215 case WP_BRYAR_PISTOL:
00216 break;
00217 case WP_BLASTER:
00218 if ( self->NPC->scriptFlags & SCF_ALT_FIRE )
00219 {
00220 attDelay += Q_irand( 0, 500 );
00221 }
00222 else
00223 {
00224 attDelay -= Q_irand( 0, 500 );
00225 }
00226 break;
00227 case WP_BOWCASTER:
00228 attDelay += Q_irand( 0, 500 );
00229 break;
00230 case WP_REPEATER:
00231 if ( !(self->NPC->scriptFlags&SCF_ALT_FIRE) )
00232 {
00233 attDelay += Q_irand( 0, 500 );
00234 }
00235 break;
00236 case WP_FLECHETTE:
00237 attDelay += Q_irand( 500, 1500 );
00238 break;
00239 case WP_ROCKET_LAUNCHER:
00240 attDelay += Q_irand( 500, 1500 );
00241 break;
00242
00243
00244
00245
00246 case WP_DISRUPTOR:
00247 return;
00248 break;
00249 case WP_THERMAL:
00250 return;
00251 break;
00252 case WP_STUN_BATON:
00253 return;
00254 break;
00255 case WP_EMPLACED_GUN:
00256 return;
00257 break;
00258 case WP_TURRET:
00259 return;
00260 break;
00261
00262
00263 break;
00264
00265
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280
00281
00282 }
00283
00284 if ( self->client->playerTeam == NPCTEAM_PLAYER )
00285 {
00286 if ( attDelay > 2000 )
00287 {
00288 attDelay = 2000;
00289 }
00290 }
00291
00292
00293 if ( attDelay > 4000+((2-g_spskill.integer)*3000) )
00294 {
00295 attDelay = 4000+((2-g_spskill.integer)*3000);
00296 }
00297 TIMER_Set( self, "attackDelay", attDelay );
00298
00299 if ( attDelay > 4000 )
00300 {
00301 attDelay = 4000 - Q_irand(500, 1500);
00302 }
00303 else
00304 {
00305 attDelay -= Q_irand(500, 1500);
00306 }
00307
00308 TIMER_Set( self, "roamTime", attDelay );
00309 }
00310 }
00311
00312 void G_ForceSaberOn(gentity_t *ent)
00313 {
00314 if (ent->client->ps.saberInFlight)
00315 {
00316 return;
00317 }
00318
00319 if (!ent->client->ps.saberHolstered)
00320 {
00321 return;
00322 }
00323
00324 if (ent->client->ps.weapon != WP_SABER)
00325 {
00326 return;
00327 }
00328
00329
00330 ent->client->ps.saberHolstered = 0;
00331
00332 if (ent->client->saber[0].soundOn)
00333 {
00334 G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOn);
00335 }
00336 if (ent->client->saber[1].soundOn)
00337 {
00338 G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOn);
00339 }
00340 }
00341
00342
00343
00344
00345
00346
00347
00348 void G_AimSet( gentity_t *self, int aim );
00349 void G_SetEnemy( gentity_t *self, gentity_t *enemy )
00350 {
00351 int event = 0;
00352
00353
00354 if ( enemy == NULL )
00355 return;
00356
00357
00358 if ( enemy->inuse == 0 )
00359 {
00360 return;
00361 }
00362
00363
00364 if ( enemy->flags & FL_NOTARGET )
00365 return;
00366
00367 if ( !self->NPC )
00368 {
00369 self->enemy = enemy;
00370 return;
00371 }
00372
00373 if ( self->NPC->confusionTime > level.time )
00374 {
00375 return;
00376 }
00377
00378 #ifdef _DEBUG
00379 if ( self->s.number )
00380 {
00381 assert( enemy != self );
00382 }
00383 #endif// _DEBUG
00384
00385
00386
00387
00388
00389
00390 if ( self->client && self->NPC && enemy->client && enemy->client->playerTeam == self->client->playerTeam )
00391 {
00392 if ( self->NPC->charmedTime > level.time )
00393 {
00394 return;
00395 }
00396 }
00397
00398 if ( self->NPC && self->client && self->client->ps.weapon == WP_SABER )
00399 {
00400
00401 NPC_Jedi_RateNewEnemy( self, enemy );
00402 }
00403
00404
00405
00406
00407 if ( self->enemy == NULL )
00408 {
00409
00410 if ( self->health > 0 )
00411 {
00412 G_ForceSaberOn(self);
00413 }
00414
00415
00416 G_ClearEnemy( self );
00417 self->enemy = enemy;
00418
00419
00420 if ( self->client->playerTeam == NPCTEAM_PLAYER && enemy->s.number == 0 )
00421 {
00422 self->client->enemyTeam = NPCTEAM_PLAYER;
00423 }
00424
00425
00426 if( G_ActivateBehavior( self, BSET_ANGER ) )
00427 {
00428 }
00429 else if ( self->client && enemy->client && self->client->playerTeam != enemy->client->playerTeam )
00430 {
00431
00432
00433
00434 if (1)
00435 {
00436 if ( !G_TeamEnemy( self ) )
00437 {
00438 event = Q_irand(EV_ANGER1, EV_ANGER3);
00439 }
00440 }
00441
00442 if ( event )
00443 {
00444 G_AddVoiceEvent( self, event, 2000 );
00445 }
00446 }
00447
00448 if ( self->s.weapon == WP_BLASTER || self->s.weapon == WP_REPEATER ||
00449 self->s.weapon == WP_THERMAL
00450 || self->s.weapon == WP_BOWCASTER )
00451 {
00452
00453
00454 if ( self->client->playerTeam == NPCTEAM_PLAYER )
00455 {
00456 G_AimSet( self, Q_irand( self->NPC->stats.aim - (5*(g_spskill.integer)), self->NPC->stats.aim - g_spskill.integer ) );
00457 }
00458 else
00459 {
00460 int minErr = 3;
00461 int maxErr = 12;
00462 if ( self->client->NPC_class == CLASS_IMPWORKER )
00463 {
00464 minErr = 15;
00465 maxErr = 30;
00466 }
00467 else if ( self->client->NPC_class == CLASS_STORMTROOPER && self->NPC && self->NPC->rank <= RANK_CREWMAN )
00468 {
00469 minErr = 5;
00470 maxErr = 15;
00471 }
00472
00473 G_AimSet( self, Q_irand( self->NPC->stats.aim - (maxErr*(3-g_spskill.integer)), self->NPC->stats.aim - (minErr*(3-g_spskill.integer)) ) );
00474 }
00475 }
00476
00477
00478 if ( Q_stricmp( "desperado", self->NPC_type ) != 0 && Q_stricmp( "paladin", self->NPC_type ) != 0 )
00479 {
00480 if ( self->client->ps.fd.forceGripBeingGripped < level.time )
00481 {
00482 G_AngerAlert( self );
00483 }
00484 }
00485
00486
00487 G_AttackDelay( self, enemy );
00488
00489
00490
00491
00492
00493
00494
00495
00496
00497
00498
00499
00500
00501
00502
00503
00504
00505
00506
00507
00508
00509
00510 return;
00511 }
00512
00513
00514
00515 if ( event )
00516 {
00517 G_AddVoiceEvent( self, event, 2000 );
00518 }
00519
00520
00521 G_ClearEnemy(self);
00522 self->enemy = enemy;
00523 }
00524
00525
00526
00527
00528
00529
00530
00531
00532
00533
00534
00535
00536
00537
00538
00539
00540
00541
00542
00543
00544
00545
00546
00547
00548
00549
00550
00551
00552
00553
00554
00555
00556
00557
00558
00559
00560
00561
00562
00563
00564
00565
00566
00567
00568
00569
00570 void ChangeWeapon( gentity_t *ent, int newWeapon )
00571 {
00572 if ( !ent || !ent->client || !ent->NPC )
00573 {
00574 return;
00575 }
00576
00577 ent->client->ps.weapon = newWeapon;
00578 ent->client->pers.cmd.weapon = newWeapon;
00579 ent->NPC->shotTime = 0;
00580 ent->NPC->burstCount = 0;
00581 ent->NPC->attackHold = 0;
00582 ent->NPC->currentAmmo = ent->client->ps.ammo[weaponData[newWeapon].ammoIndex];
00583
00584 switch ( newWeapon )
00585 {
00586 case WP_BRYAR_PISTOL:
00587 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00588 ent->NPC->burstSpacing = 1000;
00589 break;
00590
00591
00592
00593
00594
00595
00596
00597
00598
00599
00600
00601
00602
00603
00604
00605
00606
00607
00608
00609
00610
00611
00612
00613
00614
00615
00616
00617
00618 case WP_SABER:
00619 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00620 ent->NPC->burstSpacing = 0;
00621 break;
00622
00623 case WP_DISRUPTOR:
00624 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00625 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
00626 {
00627 switch( g_spskill.integer )
00628 {
00629 case 0:
00630 ent->NPC->burstSpacing = 2500;
00631 break;
00632 case 1:
00633 ent->NPC->burstSpacing = 2000;
00634 break;
00635 case 2:
00636 ent->NPC->burstSpacing = 1500;
00637 break;
00638 }
00639 }
00640 else
00641 {
00642 ent->NPC->burstSpacing = 1000;
00643 }
00644 break;
00645
00646 case WP_BOWCASTER:
00647 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00648
00649 if ( g_spskill.integer == 0 )
00650 ent->NPC->burstSpacing = 1000;
00651 else if ( g_spskill.integer == 1 )
00652 ent->NPC->burstSpacing = 750;
00653 else
00654 ent->NPC->burstSpacing = 500;
00655 break;
00656
00657 case WP_REPEATER:
00658 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
00659 {
00660 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00661 ent->NPC->burstSpacing = 2000;
00662 }
00663 else
00664 {
00665 ent->NPC->aiFlags |= NPCAI_BURST_WEAPON;
00666 ent->NPC->burstMin = 3;
00667 ent->NPC->burstMean = 6;
00668 ent->NPC->burstMax = 10;
00669 if ( g_spskill.integer == 0 )
00670 ent->NPC->burstSpacing = 1500;
00671 else if ( g_spskill.integer == 1 )
00672 ent->NPC->burstSpacing = 1000;
00673 else
00674 ent->NPC->burstSpacing = 500;
00675 }
00676 break;
00677
00678 case WP_DEMP2:
00679 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00680 ent->NPC->burstSpacing = 1000;
00681 break;
00682
00683 case WP_FLECHETTE:
00684 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00685 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
00686 {
00687 ent->NPC->burstSpacing = 2000;
00688 }
00689 else
00690 {
00691 ent->NPC->burstSpacing = 1000;
00692 }
00693 break;
00694
00695 case WP_ROCKET_LAUNCHER:
00696 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00697
00698 if ( g_spskill.integer == 0 )
00699 ent->NPC->burstSpacing = 2500;
00700 else if ( g_spskill.integer == 1 )
00701 ent->NPC->burstSpacing = 2000;
00702 else
00703 ent->NPC->burstSpacing = 1500;
00704 break;
00705
00706 case WP_THERMAL:
00707 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00708
00709 if ( g_spskill.integer == 0 )
00710 ent->NPC->burstSpacing = 3000;
00711 else if ( g_spskill.integer == 1 )
00712 ent->NPC->burstSpacing = 2500;
00713 else
00714 ent->NPC->burstSpacing = 2000;
00715 break;
00716
00717
00718
00719
00720
00721
00722
00723
00724
00725
00726
00727
00728
00729
00730
00731
00732
00733
00734
00735
00736 case WP_BLASTER:
00737 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
00738 {
00739 ent->NPC->aiFlags |= NPCAI_BURST_WEAPON;
00740 ent->NPC->burstMin = 3;
00741 ent->NPC->burstMean = 3;
00742 ent->NPC->burstMax = 3;
00743 if ( g_spskill.integer == 0 )
00744 ent->NPC->burstSpacing = 1500;
00745 else if ( g_spskill.integer == 1 )
00746 ent->NPC->burstSpacing = 1000;
00747 else
00748 ent->NPC->burstSpacing = 500;
00749 }
00750 else
00751 {
00752 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00753 if ( g_spskill.integer == 0 )
00754 ent->NPC->burstSpacing = 1000;
00755 else if ( g_spskill.integer == 1 )
00756 ent->NPC->burstSpacing = 750;
00757 else
00758 ent->NPC->burstSpacing = 500;
00759
00760 }
00761 break;
00762
00763 case WP_STUN_BATON:
00764 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00765 ent->NPC->burstSpacing = 1000;
00766 break;
00767
00768
00769
00770
00771
00772
00773
00774
00775
00776
00777
00778
00779
00780
00781
00782
00783 case WP_EMPLACED_GUN:
00784
00785 if ( ent->client && ent->client->NPC_class == CLASS_REELO )
00786 {
00787 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00788 ent->NPC->burstSpacing = 1000;
00789
00790
00791
00792
00793
00794
00795 }
00796 else
00797 {
00798 ent->NPC->aiFlags |= NPCAI_BURST_WEAPON;
00799 ent->NPC->burstMin = 2;
00800 ent->NPC->burstMean = 2;
00801 ent->NPC->burstMax = 2;
00802
00803 if ( ent->parent )
00804 {
00805 if ( g_spskill.integer == 0 )
00806 {
00807 ent->NPC->burstSpacing = ent->parent->wait + 400;
00808 ent->NPC->burstMin = ent->NPC->burstMax = 1;
00809 }
00810 else if ( g_spskill.integer == 1 )
00811 {
00812 ent->NPC->burstSpacing = ent->parent->wait + 200;
00813 }
00814 else
00815 {
00816 ent->NPC->burstSpacing = ent->parent->wait;
00817 }
00818 }
00819 else
00820 {
00821 if ( g_spskill.integer == 0 )
00822 {
00823 ent->NPC->burstSpacing = 1200;
00824 ent->NPC->burstMin = ent->NPC->burstMax = 1;
00825 }
00826 else if ( g_spskill.integer == 1 )
00827 {
00828 ent->NPC->burstSpacing = 1000;
00829 }
00830 else
00831 {
00832 ent->NPC->burstSpacing = 800;
00833 }
00834 }
00835 }
00836 break;
00837
00838 default:
00839 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00840 break;
00841 }
00842 }
00843
00844 void NPC_ChangeWeapon( int newWeapon )
00845 {
00846
00847
00848
00849
00850
00851
00852
00853
00854
00855
00856
00857
00858
00859
00860
00861
00862
00863
00864
00865
00866
00867
00868
00869
00870
00871
00872
00873 }
00874
00875
00876
00877
00878 void NPC_ApplyWeaponFireDelay(void)
00879 {
00880 if ( NPC->attackDebounceTime > level.time )
00881 {
00882
00883 return;
00884 }
00885
00886 switch(client->ps.weapon)
00887 {
00888
00889
00890
00891
00892
00893
00894
00895 case WP_THERMAL:
00896 if ( client->ps.clientNum )
00897 {
00898
00899
00900
00901
00902 client->ps.weaponTime = 700;
00903 }
00904 break;
00905
00906 case WP_STUN_BATON:
00907
00908 if (1)
00909 {
00910 client->ps.weaponTime = 300;
00911 }
00912 break;
00913
00914 default:
00915 client->ps.weaponTime = 0;
00916 break;
00917 }
00918 }
00919
00920
00921
00922
00923
00924
00925 void ShootThink( void )
00926 {
00927 int delay;
00928
00929 ucmd.buttons &= ~BUTTON_ATTACK;
00930
00931
00932
00933
00934
00935 if ( client->ps.weapon == WP_NONE )
00936 return;
00937
00938 if ( client->ps.weaponstate != WEAPON_READY && client->ps.weaponstate != WEAPON_FIRING && client->ps.weaponstate != WEAPON_IDLE)
00939 return;
00940
00941 if ( level.time < NPCInfo->shotTime )
00942 {
00943 return;
00944 }
00945
00946 ucmd.buttons |= BUTTON_ATTACK;
00947
00948 NPCInfo->currentAmmo = client->ps.ammo[weaponData[client->ps.weapon].ammoIndex];
00949
00950 NPC_ApplyWeaponFireDelay();
00951
00952 if ( NPCInfo->aiFlags & NPCAI_BURST_WEAPON )
00953 {
00954 if ( !NPCInfo->burstCount )
00955 {
00956 NPCInfo->burstCount = Q_irand( NPCInfo->burstMin, NPCInfo->burstMax );
00957
00958
00959
00960
00961
00962
00963
00964
00965
00966
00967
00968 delay = 0;
00969 }
00970 else
00971 {
00972 NPCInfo->burstCount--;
00973 if ( NPCInfo->burstCount == 0 )
00974 {
00975 delay = NPCInfo->burstSpacing;
00976 }
00977 else
00978 {
00979 delay = 0;
00980 }
00981 }
00982
00983 if ( !delay )
00984 {
00985
00986 if ( client->ps.weapon == WP_EMPLACED_GUN )
00987 {
00988 if ( NPC->parent )
00989 {
00990 if ( g_spskill.integer == 0 )
00991 {
00992 delay = NPC->parent->random + 150;
00993 }
00994 else if ( g_spskill.integer == 1 )
00995 {
00996 delay = NPC->parent->random + 100;
00997 }
00998 else
00999 {
01000 delay = NPC->parent->random;
01001 }
01002 }
01003 else
01004 {
01005 if ( g_spskill.integer == 0 )
01006 {
01007 delay = 350;
01008 }
01009 else if ( g_spskill.integer == 1 )
01010 {
01011 delay = 300;
01012 }
01013 else
01014 {
01015 delay = 200;
01016 }
01017 }
01018 }
01019 }
01020 }
01021 else
01022 {
01023 delay = NPCInfo->burstSpacing;
01024 }
01025
01026 NPCInfo->shotTime = level.time + delay;
01027 NPC->attackDebounceTime = level.time + NPC_AttackDebounceForWeapon();
01028 }
01029
01030
01031
01032
01033
01034
01035
01036 void WeaponThink( qboolean inCombat )
01037 {
01038 if ( client->ps.weaponstate == WEAPON_RAISING || client->ps.weaponstate == WEAPON_DROPPING )
01039 {
01040 ucmd.weapon = client->ps.weapon;
01041 ucmd.buttons &= ~BUTTON_ATTACK;
01042 return;
01043 }
01044
01045
01046
01047 if(NPC->client->ps.ammo[ weaponData[client->ps.weapon].ammoIndex ] < 10)
01048
01049 {
01050 Add_Ammo (NPC, client->ps.weapon, 100);
01051 }
01052
01053
01054
01055
01056
01057
01058
01059
01060
01061
01062
01063
01064
01065
01066
01067
01068
01069
01070
01071
01072
01073
01074
01075
01076
01077
01078
01079
01080
01081
01082
01083 {
01084
01085
01086
01087
01088
01089
01090
01091
01092
01093
01094
01095
01096
01097
01098
01099 }
01100
01101 ucmd.weapon = client->ps.weapon;
01102 ShootThink();
01103 }
01104
01105
01106
01107
01108
01109 qboolean HaveWeapon( int weapon )
01110 {
01111 return ( client->ps.stats[STAT_WEAPONS] & ( 1 << weapon ) );
01112 }
01113
01114 qboolean EntIsGlass (gentity_t *check)
01115 {
01116 if(check->classname &&
01117 !Q_stricmp("func_breakable", check->classname) &&
01118 check->count == 1 && check->health <= 100)
01119 {
01120 return qtrue;
01121 }
01122
01123 return qfalse;
01124 }
01125
01126 qboolean ShotThroughGlass (trace_t *tr, gentity_t *target, vec3_t spot, int mask)
01127 {
01128 gentity_t *hit = &g_entities[ tr->entityNum ];
01129 if(hit != target && EntIsGlass(hit))
01130 {
01131 int skip = hit->s.number;
01132 vec3_t muzzle;
01133
01134 VectorCopy(tr->endpos, muzzle);
01135 trap_Trace (tr, muzzle, NULL, NULL, spot, skip, mask );
01136 return qtrue;
01137 }
01138
01139 return qfalse;
01140 }
01141
01142
01143
01144
01145
01146
01147
01148
01149
01150 qboolean CanShoot ( gentity_t *ent, gentity_t *shooter )
01151 {
01152 trace_t tr;
01153 vec3_t muzzle;
01154 vec3_t spot, diff;
01155 gentity_t *traceEnt;
01156
01157 CalcEntitySpot( shooter, SPOT_WEAPON, muzzle );
01158 CalcEntitySpot( ent, SPOT_ORIGIN, spot );
01159
01160 trap_Trace ( &tr, muzzle, NULL, NULL, spot, shooter->s.number, MASK_SHOT );
01161 traceEnt = &g_entities[ tr.entityNum ];
01162
01163
01164 if (tr.startsolid && (shooter->NPC) && (shooter->NPC->touchedByPlayer) )
01165 {
01166 traceEnt = shooter->NPC->touchedByPlayer;
01167 }
01168
01169 if ( ShotThroughGlass( &tr, ent, spot, MASK_SHOT ) )
01170 {
01171 traceEnt = &g_entities[ tr.entityNum ];
01172 }
01173
01174
01175 if ( traceEnt == ent )
01176 {
01177 return qtrue;
01178 }
01179
01180 else
01181 {
01182 CalcEntitySpot( ent, SPOT_HEAD, spot );
01183 trap_Trace ( &tr, muzzle, NULL, NULL, spot, shooter->s.number, MASK_SHOT );
01184 traceEnt = &g_entities[ tr.entityNum ];
01185 if ( traceEnt == ent)
01186 {
01187 return qtrue;
01188 }
01189 }
01190
01191
01192
01193 VectorSubtract(spot, tr.endpos, diff);
01194 if(VectorLength(diff) < random() * 32)
01195 {
01196 return qtrue;
01197 }
01198
01199
01200 if ( !traceEnt->client )
01201 {
01202 return qfalse;
01203 }
01204
01205
01206
01207
01208 if ( traceEnt->health <= 0 )
01209 {
01210 return qtrue;
01211 }
01212
01213
01214 if ( traceEnt->client && ( traceEnt->client->playerTeam == shooter->client->playerTeam ) )
01215 {
01216 return qfalse;
01217 }
01218
01219
01220 return qtrue;
01221 }
01222
01223
01224
01225
01226
01227
01228
01229 void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis )
01230 {
01231
01232 if ( other == NPC->enemy )
01233 return;
01234
01235 if ( other->flags & FL_NOTARGET )
01236 return;
01237
01238
01239 if ( NPC->enemy && vis == VIS_FOV )
01240 {
01241 if ( NPCInfo->enemyLastSeenTime - level.time < 2000 )
01242 {
01243 return;
01244 }
01245 if ( enemyVisibility == VIS_UNKNOWN )
01246 {
01247 enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_360|CHECK_FOV );
01248 }
01249 if ( enemyVisibility == VIS_FOV )
01250 {
01251 return;
01252 }
01253 }
01254
01255 if ( !NPC->enemy )
01256 {
01257 G_SetEnemy( NPC, other );
01258 }
01259
01260 if ( vis == VIS_FOV )
01261 {
01262 NPCInfo->enemyLastSeenTime = level.time;
01263 VectorCopy( other->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
01264 NPCInfo->enemyLastHeardTime = 0;
01265 VectorClear( NPCInfo->enemyLastHeardLocation );
01266 }
01267 else
01268 {
01269 NPCInfo->enemyLastSeenTime = 0;
01270 VectorClear( NPCInfo->enemyLastSeenLocation );
01271 NPCInfo->enemyLastHeardTime = level.time;
01272 VectorCopy( other->r.currentOrigin, NPCInfo->enemyLastHeardLocation );
01273 }
01274 }
01275
01276
01277
01278
01279
01280
01281
01282
01283
01284
01285
01286
01287
01288 int NPC_AttackDebounceForWeapon (void)
01289 {
01290 switch ( NPC->client->ps.weapon )
01291 {
01292
01293
01294
01295
01296
01297
01298
01299
01300
01301
01302
01303
01304
01305
01306
01307
01308
01309
01310 case WP_SABER:
01311 return 0;
01312 break;
01313
01314
01315
01316
01317
01318
01319
01320
01321
01322
01323
01324
01325
01326
01327 default:
01328 return NPCInfo->burstSpacing;
01329 break;
01330 }
01331 }
01332
01333
01334 float NPC_MaxDistSquaredForWeapon (void)
01335 {
01336 if(NPCInfo->stats.shootDistance > 0)
01337 {
01338 return NPCInfo->stats.shootDistance * NPCInfo->stats.shootDistance;
01339 }
01340
01341 switch ( NPC->s.weapon )
01342 {
01343 case WP_BLASTER:
01344 return 1024 * 1024;
01345 break;
01346
01347 case WP_BRYAR_PISTOL:
01348 return 1024 * 1024;
01349 break;
01350
01351
01352
01353
01354
01355
01356
01357 case WP_DISRUPTOR:
01358 if ( NPCInfo->scriptFlags & SCF_ALT_FIRE )
01359 {
01360 return ( 4096 * 4096 );
01361 }
01362 else
01363 {
01364 return 1024 * 1024;
01365 }
01366 break;
01367
01368
01369
01370
01371
01372
01373
01374
01375
01376
01377 case WP_SABER:
01378 if ( NPC->client && NPC->client->saber[0].blade[0].lengthMax )
01379 {
01380 return (NPC->client->saber[0].blade[0].lengthMax + NPC->r.maxs[0]*1.5)*(NPC->client->saber[0].blade[0].lengthMax + NPC->r.maxs[0]*1.5);
01381 }
01382 else
01383 {
01384 return 48*48;
01385 }
01386 break;
01387
01388 default:
01389 return 1024 * 1024;
01390 break;
01391 }
01392 }
01393
01394
01395
01396
01397
01398
01399
01400 qboolean ValidEnemy(gentity_t *ent)
01401 {
01402 if ( ent == NULL )
01403 return qfalse;
01404
01405 if ( ent == NPC )
01406 return qfalse;
01407
01408
01409
01410
01411
01412 if ( !(ent->flags & FL_NOTARGET) )
01413 {
01414 if( ent->health > 0 )
01415 {
01416 if( !ent->client )
01417 {
01418 return qtrue;
01419 }
01420 else if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR )
01421 {
01422 return qfalse;
01423 }
01424 else
01425 {
01426 int entTeam = TEAM_FREE;
01427 if ( ent->NPC && ent->client )
01428 {
01429 entTeam = ent->client->playerTeam;
01430 }
01431 else if ( ent->client )
01432 {
01433 if ( ent->client->sess.sessionTeam == TEAM_BLUE )
01434 {
01435 entTeam = NPCTEAM_PLAYER;
01436 }
01437 else if ( ent->client->sess.sessionTeam == TEAM_RED )
01438 {
01439 entTeam = NPCTEAM_ENEMY;
01440 }
01441 else
01442 {
01443 entTeam = NPCTEAM_NEUTRAL;
01444 }
01445 }
01446 if( entTeam == NPCTEAM_FREE
01447 || NPC->client->enemyTeam == NPCTEAM_FREE
01448 || entTeam == NPC->client->enemyTeam )
01449 {
01450 if ( entTeam != NPC->client->playerTeam )
01451 {
01452 return qtrue;
01453 }
01454 }
01455 }
01456 }
01457 }
01458
01459 return qfalse;
01460 }
01461
01462 qboolean NPC_EnemyTooFar(gentity_t *enemy, float dist, qboolean toShoot)
01463 {
01464 vec3_t vec;
01465
01466
01467 if ( !toShoot )
01468 {
01469 if ( NPC->client->ps.weapon == WP_SABER )
01470 {
01471 return qfalse;
01472 }
01473 }
01474
01475
01476 if(!dist)
01477 {
01478 VectorSubtract(NPC->r.currentOrigin, enemy->r.currentOrigin, vec);
01479 dist = VectorLengthSquared(vec);
01480 }
01481
01482 if(dist > NPC_MaxDistSquaredForWeapon())
01483 return qtrue;
01484
01485 return qfalse;
01486 }
01487
01488
01489
01490
01491
01492
01493
01494
01495
01496
01497
01498
01499
01500
01501
01502
01503
01504
01505 gentity_t *NPC_PickEnemy( gentity_t *closestTo, int enemyTeam, qboolean checkVis, qboolean findPlayersFirst, qboolean findClosest )
01506 {
01507 int num_choices = 0;
01508 int choice[128];
01509 gentity_t *newenemy = NULL;
01510 gentity_t *closestEnemy = NULL;
01511 int entNum;
01512 vec3_t diff;
01513 float relDist;
01514 float bestDist = Q3_INFINITE;
01515 qboolean failed = qfalse;
01516 int visChecks = (CHECK_360|CHECK_FOV|CHECK_VISRANGE);
01517 int minVis = VIS_FOV;
01518
01519 if ( enemyTeam == NPCTEAM_NEUTRAL )
01520 {
01521 return NULL;
01522 }
01523
01524 if ( NPCInfo->behaviorState == BS_STAND_AND_SHOOT ||
01525 NPCInfo->behaviorState == BS_HUNT_AND_KILL )
01526 {
01527
01528
01529 visChecks &= ~CHECK_FOV;
01530 minVis = VIS_360;
01531 }
01532
01533 if( findPlayersFirst )
01534 {
01535 newenemy = &g_entities[0];
01536 if( newenemy->client && !(newenemy->flags & FL_NOTARGET) && !(newenemy->s.eFlags & EF_NODRAW))
01537 {
01538 if( newenemy->health > 0 )
01539 {
01540 if( NPC_ValidEnemy( newenemy) )
01541 {
01542 if( newenemy != NPC->lastEnemy )
01543 {
01544 if ( trap_InPVS(newenemy->r.currentOrigin, NPC->r.currentOrigin) )
01545 {
01546 if(NPCInfo->behaviorState == BS_INVESTIGATE || NPCInfo->behaviorState == BS_PATROL)
01547 {
01548 if(!NPC->enemy)
01549 {
01550 if(!InVisrange(newenemy))
01551 {
01552 failed = qtrue;
01553 }
01554 else if(NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) != VIS_FOV)
01555 {
01556 failed = qtrue;
01557 }
01558 }
01559 }
01560
01561 if ( !failed )
01562 {
01563 VectorSubtract( closestTo->r.currentOrigin, newenemy->r.currentOrigin, diff );
01564 relDist = VectorLengthSquared(diff);
01565 if ( newenemy->client->hiddenDist > 0 )
01566 {
01567 if( relDist > newenemy->client->hiddenDist*newenemy->client->hiddenDist )
01568 {
01569
01570 if ( VectorLengthSquared( newenemy->client->hiddenDir ) )
01571 {
01572 float dot;
01573 VectorNormalize( diff );
01574 dot = DotProduct( newenemy->client->hiddenDir, diff );
01575 if ( dot > 0.5 )
01576 {
01577 failed = qtrue;
01578 }
01579 else
01580 {
01581 Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDir %s targetDir %s dot %f\n", NPC->targetname, newenemy->targetname, vtos(newenemy->client->hiddenDir), vtos(diff), dot );
01582 }
01583 }
01584 else
01585 {
01586 failed = qtrue;
01587 }
01588 }
01589 else
01590 {
01591 Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDist %f\n", NPC->targetname, newenemy->targetname, newenemy->client->hiddenDist );
01592 }
01593 }
01594
01595 if(!failed)
01596 {
01597 if(findClosest)
01598 {
01599 if(relDist < bestDist)
01600 {
01601 if(!NPC_EnemyTooFar(newenemy, relDist, qfalse))
01602 {
01603 if(checkVis)
01604 {
01605 if( NPC_CheckVisibility ( newenemy, visChecks ) == minVis )
01606 {
01607 bestDist = relDist;
01608 closestEnemy = newenemy;
01609 }
01610 }
01611 else
01612 {
01613 bestDist = relDist;
01614 closestEnemy = newenemy;
01615 }
01616 }
01617 }
01618 }
01619 else if(!NPC_EnemyTooFar(newenemy, 0, qfalse))
01620 {
01621 if(checkVis)
01622 {
01623 if( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) == VIS_FOV )
01624 {
01625 choice[num_choices++] = newenemy->s.number;
01626 }
01627 }
01628 else
01629 {
01630 choice[num_choices++] = newenemy->s.number;
01631 }
01632 }
01633 }
01634 }
01635 }
01636 }
01637 }
01638 }
01639 }
01640 }
01641
01642 if (findClosest && closestEnemy)
01643 {
01644 return closestEnemy;
01645 }
01646
01647 if (num_choices)
01648 {
01649 return &g_entities[ choice[rand() % num_choices] ];
01650 }
01651
01652
01653
01654
01655
01656
01657
01658
01659
01660 num_choices = 0;
01661 bestDist = Q3_INFINITE;
01662 closestEnemy = NULL;
01663
01664 for ( entNum = 0; entNum < level.num_entities; entNum++ )
01665 {
01666 newenemy = &g_entities[entNum];
01667
01668 if ( newenemy != NPC && (newenemy->client ) && !(newenemy->flags & FL_NOTARGET) && !(newenemy->s.eFlags & EF_NODRAW))
01669 {
01670 if ( newenemy->health > 0 )
01671 {
01672 if ( (newenemy->client && NPC_ValidEnemy( newenemy))
01673 || (!newenemy->client && newenemy->alliedTeam == enemyTeam) )
01674 {
01675 if ( NPC->client->playerTeam == NPCTEAM_PLAYER && enemyTeam == NPCTEAM_PLAYER )
01676 {
01677 if ( newenemy->s.number )
01678 {
01679 continue;
01680 }
01681 }
01682
01683 if ( newenemy != NPC->lastEnemy )
01684 {
01685 if(!trap_InPVS(newenemy->r.currentOrigin, NPC->r.currentOrigin))
01686 {
01687 continue;
01688 }
01689
01690 if ( NPCInfo->behaviorState == BS_INVESTIGATE || NPCInfo->behaviorState == BS_PATROL )
01691 {
01692 if ( !NPC->enemy )
01693 {
01694 if ( !InVisrange( newenemy ) )
01695 {
01696 continue;
01697 }
01698 else if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) != VIS_FOV )
01699 {
01700 continue;
01701 }
01702 }
01703 }
01704
01705 VectorSubtract( closestTo->r.currentOrigin, newenemy->r.currentOrigin, diff );
01706 relDist = VectorLengthSquared(diff);
01707 if ( newenemy->client && newenemy->client->hiddenDist > 0 )
01708 {
01709 if( relDist > newenemy->client->hiddenDist*newenemy->client->hiddenDist )
01710 {
01711
01712 if ( VectorLengthSquared( newenemy->client->hiddenDir ) )
01713 {
01714 float dot;
01715
01716 VectorNormalize( diff );
01717 dot = DotProduct( newenemy->client->hiddenDir, diff );
01718 if ( dot > 0.5 )
01719 {
01720 continue;
01721 }
01722 else
01723 {
01724 Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDir %s targetDir %s dot %f\n", NPC->targetname, newenemy->targetname, vtos(newenemy->client->hiddenDir), vtos(diff), dot );
01725 }
01726 }
01727 else
01728 {
01729 continue;
01730 }
01731 }
01732 else
01733 {
01734 Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDist %f\n", NPC->targetname, newenemy->targetname, newenemy->client->hiddenDist );
01735 }
01736 }
01737
01738 if ( findClosest )
01739 {
01740 if ( relDist < bestDist )
01741 {
01742 if ( !NPC_EnemyTooFar( newenemy, relDist, qfalse ) )
01743 {
01744 if ( checkVis )
01745 {
01746
01747
01748
01749 if ( NPC_CheckVisibility ( newenemy, visChecks ) == minVis )
01750 {
01751 bestDist = relDist;
01752 closestEnemy = newenemy;
01753 }
01754 }
01755 else
01756 {
01757 bestDist = relDist;
01758 closestEnemy = newenemy;
01759 }
01760 }
01761 }
01762 }
01763 else if ( !NPC_EnemyTooFar( newenemy, 0, qfalse ) )
01764 {
01765 if ( checkVis )
01766 {
01767
01768 if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_VISRANGE ) >= VIS_360 )
01769 {
01770 choice[num_choices++] = newenemy->s.number;
01771 }
01772 }
01773 else
01774 {
01775 choice[num_choices++] = newenemy->s.number;
01776 }
01777 }
01778 }
01779 }
01780 }
01781 }
01782 }
01783
01784
01785 if (findClosest)
01786 {
01787 return closestEnemy;
01788 }
01789
01790 if (!num_choices)
01791 {
01792 return NULL;
01793 }
01794
01795 return &g_entities[ choice[rand() % num_choices] ];
01796 }
01797
01798
01799
01800
01801
01802
01803
01804 gentity_t *NPC_PickAlly ( qboolean facingEachOther, float range, qboolean ignoreGroup, qboolean movingOnly )
01805 {
01806 gentity_t *ally = NULL;
01807 gentity_t *closestAlly = NULL;
01808 int entNum;
01809 vec3_t diff;
01810 float relDist;
01811 float bestDist = range;
01812
01813 for ( entNum = 0; entNum < level.num_entities; entNum++ )
01814 {
01815 ally = &g_entities[entNum];
01816
01817 if ( ally->client )
01818 {
01819 if ( ally->health > 0 )
01820 {
01821 if ( ally->client && ( ally->client->playerTeam == NPC->client->playerTeam ||
01822 NPC->client->playerTeam == NPCTEAM_ENEMY ) )
01823 {
01824 if ( ignoreGroup )
01825 {
01826 if ( ally == NPC->client->leader )
01827 {
01828
01829 continue;
01830 }
01831 if ( ally->client && ally->client->leader && ally->client->leader == NPC )
01832 {
01833
01834 continue;
01835 }
01836 }
01837
01838 if(!trap_InPVS(ally->r.currentOrigin, NPC->r.currentOrigin))
01839 {
01840 continue;
01841 }
01842
01843 if ( movingOnly && ally->client && NPC->client )
01844 {
01845 if ( !DistanceSquared( ally->client->ps.velocity, NPC->client->ps.velocity ) )
01846 {
01847 continue;
01848 }
01849 }
01850
01851 VectorSubtract( NPC->r.currentOrigin, ally->r.currentOrigin, diff );
01852 relDist = VectorNormalize( diff );
01853 if ( relDist < bestDist )
01854 {
01855 if ( facingEachOther )
01856 {
01857 vec3_t vf;
01858 float dot;
01859
01860 AngleVectors( ally->client->ps.viewangles, vf, NULL, NULL );
01861 VectorNormalize(vf);
01862 dot = DotProduct(diff, vf);
01863
01864 if ( dot < 0.5 )
01865 {
01866 continue;
01867 }
01868
01869 AngleVectors( NPC->client->ps.viewangles, vf, NULL, NULL );
01870 VectorNormalize(vf);
01871 dot = DotProduct(diff, vf);
01872
01873 if ( dot > -0.5 )
01874 {
01875 continue;
01876 }
01877
01878 }
01879
01880 if ( NPC_CheckVisibility ( ally, CHECK_360|CHECK_VISRANGE ) >= VIS_360 )
01881 {
01882 bestDist = relDist;
01883 closestAlly = ally;
01884 }
01885 }
01886 }
01887 }
01888 }
01889 }
01890
01891
01892 return closestAlly;
01893 }
01894
01895 gentity_t *NPC_CheckEnemy( qboolean findNew, qboolean tooFarOk, qboolean setEnemy )
01896 {
01897 qboolean forcefindNew = qfalse;
01898 gentity_t *closestTo;
01899 gentity_t *newEnemy = NULL;
01900
01901
01902
01903
01904
01905
01906
01907
01908
01909
01910
01911 if ( NPC->enemy )
01912 {
01913 if ( !NPC->enemy->inuse )
01914 {
01915 if ( setEnemy )
01916 {
01917 G_ClearEnemy( NPC );
01918 }
01919 }
01920 }
01921
01922
01923 if (0)
01924 {
01925 if ( setEnemy )
01926 {
01927 G_ClearEnemy( NPC );
01928 }
01929 return NULL;
01930 }
01931
01932
01933
01934
01935
01936
01937
01938
01939
01940
01941
01942
01943
01944
01945
01946
01947 if ( NPC->enemy )
01948 {
01949 if ( NPC_EnemyTooFar(NPC->enemy, 0, qfalse) )
01950 {
01951 if(findNew)
01952 {
01953 forcefindNew = qtrue;
01954 }
01955 else if(!tooFarOk)
01956 {
01957 if ( setEnemy )
01958 {
01959 G_ClearEnemy( NPC );
01960 }
01961 }
01962 }
01963 else if ( !trap_InPVS(NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) )
01964 {
01965
01966
01967
01968
01969 if ( NPC->enemy->client && NPC->enemy->client->hiddenDist )
01970 {
01971
01972 NPC_LostEnemyDecideChase();
01973 }
01974 else
01975 {
01976
01977
01978
01979
01980
01981
01982
01983
01984
01985
01986
01987
01988
01989
01990
01991
01992
01993
01994
01995
01996
01997
01998 }
01999 }
02000 }
02001
02002 if ( NPC->enemy )
02003 {
02004 if ( NPC->enemy->health <= 0 || NPC->enemy->flags & FL_NOTARGET )
02005 {
02006 if ( setEnemy )
02007 {
02008 G_ClearEnemy( NPC );
02009 }
02010 }
02011 }
02012
02013 closestTo = NPC;
02014
02015
02016 if ( NPCInfo->defendEnt )
02017 {
02018 if ( NPCInfo->defendEnt->health > 0 )
02019 {
02020 if ( NPCInfo->defendEnt->enemy )
02021 {
02022 if ( NPC->enemy != NPCInfo->defendEnt->enemy )
02023 {
02024 newEnemy = NPCInfo->defendEnt->enemy;
02025 if ( setEnemy )
02026 {
02027 G_SetEnemy( NPC, NPCInfo->defendEnt->enemy );
02028 }
02029 }
02030 }
02031 else if ( NPC->enemy == NULL )
02032 {
02033 closestTo = NPCInfo->defendEnt;
02034 }
02035 }
02036 }
02037
02038 if (!NPC->enemy || ( NPC->enemy && NPC->enemy->health <= 0 ) || forcefindNew )
02039 {
02040
02041
02042
02043
02044 qboolean foundenemy = qfalse;
02045
02046 if(!findNew)
02047 {
02048 if ( setEnemy )
02049 {
02050 NPC->lastEnemy = NPC->enemy;
02051 G_ClearEnemy(NPC);
02052 }
02053 return NULL;
02054 }
02055
02056
02057 if ( NPC->client->enemyTeam != NPCTEAM_NEUTRAL )
02058 {
02059
02060
02061
02062
02063 newEnemy = NPC_PickEnemy( closestTo, NPC->client->enemyTeam, qtrue, qfalse, qtrue );
02064 if ( newEnemy )
02065 {
02066 foundenemy = qtrue;
02067 if ( setEnemy )
02068 {
02069 G_SetEnemy( NPC, newEnemy );
02070 }
02071 }
02072 }
02073
02074 if ( !forcefindNew )
02075 {
02076 if ( !foundenemy )
02077 {
02078 if ( setEnemy )
02079 {
02080 NPC->lastEnemy = NPC->enemy;
02081 G_ClearEnemy(NPC);
02082 }
02083 }
02084
02085 NPC->cantHitEnemyCounter = 0;
02086 }
02087
02088 }
02089
02090 if ( NPC->enemy && NPC->enemy->client )
02091 {
02092 if(NPC->enemy->client->playerTeam)
02093 {
02094
02095 if( NPC->client->playerTeam != NPC->enemy->client->playerTeam )
02096 {
02097 NPC->client->enemyTeam = NPC->enemy->client->playerTeam;
02098 }
02099 }
02100 }
02101 return newEnemy;
02102 }
02103
02104
02105
02106
02107
02108
02109
02110 qboolean NPC_ClearShot( gentity_t *ent )
02111 {
02112 vec3_t muzzle;
02113 trace_t tr;
02114
02115 if ( ( NPC == NULL ) || ( ent == NULL ) )
02116 return qfalse;
02117
02118 CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
02119
02120
02121
02122
02123 if( NPC->s.weapon == WP_BLASTER )
02124 {
02125 vec3_t mins = { -2, -2, -2 };
02126 vec3_t maxs = { 2, 2, 2 };
02127
02128 trap_Trace ( &tr, muzzle, mins, maxs, ent->r.currentOrigin, NPC->s.number, MASK_SHOT );
02129 }
02130 else
02131 {
02132 trap_Trace ( &tr, muzzle, NULL, NULL, ent->r.currentOrigin, NPC->s.number, MASK_SHOT );
02133 }
02134
02135 if ( tr.startsolid || tr.allsolid )
02136 {
02137 return qfalse;
02138 }
02139
02140 if ( tr.entityNum == ent->s.number )
02141 return qtrue;
02142
02143 return qfalse;
02144 }
02145
02146
02147
02148
02149
02150
02151
02152 int NPC_ShotEntity( gentity_t *ent, vec3_t impactPos )
02153 {
02154 vec3_t muzzle;
02155 vec3_t targ;
02156 trace_t tr;
02157
02158 if ( ( NPC == NULL ) || ( ent == NULL ) )
02159 return qfalse;
02160
02161 if ( NPC->s.weapon == WP_THERMAL )
02162 {
02163
02164 vec3_t angles, forward, end;
02165
02166 CalcEntitySpot( NPC, SPOT_HEAD, muzzle );
02167 VectorSet( angles, 0, NPC->client->ps.viewangles[1], 0 );
02168 AngleVectors( angles, forward, NULL, NULL );
02169 VectorMA( muzzle, 8, forward, end );
02170 end[2] += 24;
02171 trap_Trace ( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT );
02172 VectorCopy( tr.endpos, muzzle );
02173 }
02174 else
02175 {
02176 CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
02177 }
02178 CalcEntitySpot( ent, SPOT_CHEST, targ );
02179
02180
02181
02182
02183 if( NPC->s.weapon == WP_BLASTER )
02184 {
02185 vec3_t mins = { -2, -2, -2 };
02186 vec3_t maxs = { 2, 2, 2 };
02187
02188 trap_Trace ( &tr, muzzle, mins, maxs, targ, NPC->s.number, MASK_SHOT );
02189 }
02190 else
02191 {
02192 trap_Trace ( &tr, muzzle, NULL, NULL, targ, NPC->s.number, MASK_SHOT );
02193 }
02194
02195 if ( impactPos )
02196 {
02197 VectorCopy( tr.endpos, impactPos );
02198 }
02199
02200
02201
02202
02203
02204
02205 return tr.entityNum;
02206 }
02207
02208 qboolean NPC_EvaluateShot( int hit, qboolean glassOK )
02209 {
02210 if ( !NPC->enemy )
02211 {
02212 return qfalse;
02213 }
02214
02215 if ( hit == NPC->enemy->s.number || (&g_entities[hit] != NULL && (g_entities[hit].r.svFlags&SVF_GLASS_BRUSH)) )
02216 {
02217 return qtrue;
02218 }
02219 return qfalse;
02220 }
02221
02222
02223
02224
02225
02226
02227
02228 qboolean NPC_CheckAttack (float scale)
02229 {
02230 if(!scale)
02231 scale = 1.0;
02232
02233 if(((float)NPCInfo->stats.aggression) * scale < flrand(0, 4))
02234 {
02235 return qfalse;
02236 }
02237
02238 if(NPCInfo->shotTime > level.time)
02239 return qfalse;
02240
02241 return qtrue;
02242 }
02243
02244
02245
02246
02247
02248
02249
02250 qboolean NPC_CheckDefend (float scale)
02251 {
02252 if(!scale)
02253 scale = 1.0;
02254
02255 if((float)(NPCInfo->stats.evasion) > random() * 4 * scale)
02256 return qtrue;
02257
02258 return qfalse;
02259 }
02260
02261
02262
02263 qboolean NPC_CheckCanAttack (float attack_scale, qboolean stationary)
02264 {
02265 vec3_t delta, forward;
02266 vec3_t angleToEnemy;
02267 vec3_t hitspot, muzzle, diff, enemy_org;
02268 float distanceToEnemy;
02269 qboolean attack_ok = qfalse;
02270
02271 qboolean dead_on = qfalse;
02272 float aim_off;
02273 float max_aim_off = 128 - (16 * (float)NPCInfo->stats.aim);
02274 trace_t tr;
02275 gentity_t *traceEnt = NULL;
02276
02277 if(NPC->enemy->flags & FL_NOTARGET)
02278 {
02279 return qfalse;
02280 }
02281
02282
02283
02284 if(!attack_scale)
02285 {
02286 attack_scale = 1.0;
02287 }
02288
02289 CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org );
02290 NPC_AimWiggle( enemy_org );
02291
02292 CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
02293
02294 VectorSubtract (enemy_org, muzzle, delta);
02295 vectoangles ( delta, angleToEnemy );
02296 distanceToEnemy = VectorNormalize(delta);
02297
02298 NPC->NPC->desiredYaw = angleToEnemy[YAW];
02299 NPC_UpdateFiringAngles(qfalse, qtrue);
02300
02301 if( NPC_EnemyTooFar(NPC->enemy, distanceToEnemy*distanceToEnemy, qtrue) )
02302 {
02303 return qfalse;
02304 }
02305
02306 if(client->ps.weaponTime > 0)
02307 {
02308 NPC->NPC->desiredPitch = angleToEnemy[PITCH];
02309 NPC_UpdateFiringAngles(qtrue, qfalse);
02310 return qfalse;
02311 }
02312
02313 if(NPCInfo->scriptFlags & SCF_DONT_FIRE)
02314 {
02315 return qfalse;
02316 }
02317
02318 NPCInfo->enemyLastVisibility = enemyVisibility;
02319
02320 enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_360|CHECK_FOV);
02321
02322 if(enemyVisibility >= VIS_FOV)
02323 {
02324
02325 attack_ok = qtrue;
02326
02327
02328
02329 if ( NPC->enemy->client )
02330 {
02331 if ( NPC->enemy->enemy == NPC )
02332 {
02333 if ( NPC->enemy->client->buttons & BUTTON_ATTACK )
02334 {
02335 if ( NPC_CheckDefend( 1.0 ) )
02336 {
02337 attack_ok = qfalse;
02338 ucmd.upmove = -127;
02339 }
02340 }
02341 }
02342 }
02343
02344 if(attack_ok)
02345 {
02346
02347
02348 AngleVectors( client->ps.viewangles, forward, NULL, NULL );
02349 VectorMA( muzzle, distanceToEnemy, forward, hitspot );
02350 trap_Trace( &tr, muzzle, NULL, NULL, hitspot, NPC->s.number, MASK_SHOT );
02351 ShotThroughGlass( &tr, NPC->enemy, hitspot, MASK_SHOT );
02352
02353
02354
02355
02356
02357
02358 traceEnt = &g_entities[tr.entityNum];
02359
02360
02361
02362
02363
02364
02365
02366
02367
02368
02369
02370
02371
02372
02373
02374
02375
02376 VectorCopy( tr.endpos, hitspot );
02377
02378 if( traceEnt == NPC->enemy || (traceEnt->client && NPC->client->enemyTeam && NPC->client->enemyTeam == traceEnt->client->playerTeam) )
02379 {
02380 dead_on = qtrue;
02381 }
02382 else
02383 {
02384 attack_scale *= 0.5;
02385 if(NPC->client->playerTeam)
02386 {
02387 if(traceEnt && traceEnt->client && traceEnt->client->playerTeam)
02388 {
02389 if(NPC->client->playerTeam == traceEnt->client->playerTeam)
02390 {
02391 attack_ok = qfalse;
02392 }
02393 }
02394 }
02395 }
02396 }
02397
02398 if( attack_ok )
02399 {
02400
02401 VectorSubtract (hitspot, muzzle, delta);
02402 vectoangles ( delta, angleToEnemy );
02403 NPC->NPC->desiredPitch = angleToEnemy[PITCH];
02404 NPC_UpdateFiringAngles(qtrue, qfalse);
02405
02406 if( !dead_on )
02407 {
02408
02409 if(traceEnt && (traceEnt->health <= 30 || EntIsGlass(traceEnt)))
02410 {
02411
02412 if (0)
02413 {
02414 VectorSubtract(NPC->r.currentOrigin, traceEnt->r.currentOrigin, diff);
02415 if(VectorLengthSquared(diff) < traceEnt->splashRadius*traceEnt->splashRadius)
02416 {
02417 attack_ok = qfalse;
02418 }
02419 else
02420 {
02421 attack_scale *= 2;
02422 }
02423 }
02424 }
02425 else
02426 {
02427 AngleVectors (client->ps.viewangles, forward, NULL, NULL);
02428 VectorMA ( muzzle, distanceToEnemy, forward, hitspot);
02429 VectorSubtract(hitspot, enemy_org, diff);
02430 aim_off = VectorLength(diff);
02431 if(aim_off > random() * max_aim_off)
02432 {
02433 attack_scale *= 0.75;
02434
02435 VectorSubtract(hitspot, enemy_org, diff);
02436 aim_off = VectorLength(diff);
02437 if(aim_off > random() * max_aim_off)
02438 {
02439 attack_ok = qfalse;
02440 }
02441 }
02442 attack_scale *= (max_aim_off - aim_off + 1)/max_aim_off;
02443 }
02444 }
02445 }
02446 }
02447 else
02448 {
02449 NPC->NPC->desiredPitch = angleToEnemy[PITCH];
02450 NPC_UpdateFiringAngles(qtrue, qfalse);
02451 }
02452
02453 if( attack_ok )
02454 {
02455 if( NPC_CheckAttack( attack_scale ))
02456 {
02457 enemyVisibility = VIS_SHOOT;
02458 WeaponThink(qtrue);
02459 }
02460 else
02461 attack_ok = qfalse;
02462 }
02463
02464 return attack_ok;
02465 }
02466
02467
02468
02469
02470
02471
02472
02473
02474
02475 float IdealDistance ( gentity_t *self )
02476 {
02477 float ideal;
02478
02479 ideal = 225 - 20 * NPCInfo->stats.aggression;
02480 switch ( NPC->s.weapon )
02481 {
02482 case WP_ROCKET_LAUNCHER:
02483 ideal += 200;
02484 break;
02485
02486 case WP_THERMAL:
02487 ideal += 50;
02488 break;
02489
02490
02491
02492
02493
02494 case WP_SABER:
02495 case WP_BRYAR_PISTOL:
02496
02497 case WP_BLASTER:
02498 default:
02499 break;
02500 }
02501
02502 return ideal;
02503 }
02504
02505
02506
02507
02508
02509
02510
02511
02512
02513
02514
02515
02516 void SP_point_combat( gentity_t *self )
02517 {
02518 if(level.numCombatPoints >= MAX_COMBAT_POINTS)
02519 {
02520 #ifndef FINAL_BUILD
02521 Com_Printf(S_COLOR_RED"ERROR: Too many combat points, limit is %d\n", MAX_COMBAT_POINTS);
02522 #endif
02523 G_FreeEntity(self);
02524 return;
02525 }
02526
02527 self->s.origin[2] += 0.125;
02528 G_SetOrigin(self, self->s.origin);
02529 trap_LinkEntity(self);
02530
02531 if ( G_CheckInSolid( self, qtrue ) )
02532 {
02533 #ifndef FINAL_BUILD
02534 Com_Printf( S_COLOR_RED"ERROR: combat point at %s in solid!\n", vtos(self->r.currentOrigin) );
02535 #endif
02536 }
02537
02538 VectorCopy( self->r.currentOrigin, level.combatPoints[level.numCombatPoints].origin );
02539
02540 level.combatPoints[level.numCombatPoints].flags = self->spawnflags;
02541 level.combatPoints[level.numCombatPoints].occupied = qfalse;
02542
02543 level.numCombatPoints++;
02544
02545 G_FreeEntity(self);
02546 }
02547
02548 void CP_FindCombatPointWaypoints( void )
02549 {
02550 int i;
02551
02552 for ( i = 0; i < level.numCombatPoints; i++ )
02553 {
02554 level.combatPoints[i].waypoint = NAV_FindClosestWaypointForPoint2( level.combatPoints[i].origin );
02555 #ifndef FINAL_BUILD
02556 if ( level.combatPoints[i].waypoint == WAYPOINT_NONE )
02557 {
02558 Com_Printf( S_COLOR_RED"ERROR: Combat Point at %s has no waypoint!\n", vtos(level.combatPoints[i].origin) );
02559 }
02560 #endif
02561 }
02562 }
02563
02564
02565
02566
02567
02568
02569
02570 typedef struct
02571 {
02572 float dist;
02573 int index;
02574 } combatPt_t;
02575 static int NPC_CollectCombatPoints( const vec3_t origin, const float radius, combatPt_t *points, const int flags )
02576 {
02577 float radiusSqr = (radius*radius);
02578 float distance;
02579 float bestDistance = Q3_INFINITE;
02580 int bestPoint = 0;
02581 int numPoints = 0;
02582 int i;
02583
02584
02585 for ( i = 0; i < level.numCombatPoints; i++ )
02586 {
02587 if (numPoints >= MAX_COMBAT_POINTS)
02588 {
02589 break;
02590 }
02591
02592
02593 if ( level.combatPoints[i].occupied == (int) qtrue )
02594 continue;
02595
02596
02597 if ( ( flags & CP_DUCK ) && ( level.combatPoints[i].flags & CPF_DUCK ) )
02598 continue;
02599
02600
02601 if ( ( flags & CP_FLEE ) && ( level.combatPoints[i].flags & CPF_FLEE ) )
02602 continue;
02603
02605 if ( ( flags & CP_INVESTIGATE ) && ( level.combatPoints[i].flags & CPF_INVESTIGATE ) )
02606 continue;
02607
02608
02609 if ( ( level.combatPoints[i].flags & CPF_SQUAD ) && ( ( flags & CP_SQUAD ) == qfalse ) )
02610 continue;
02611
02612 if ( flags&CP_NO_PVS )
02613 {
02614 if ( trap_InPVS( origin, level.combatPoints[i].origin ) )
02615 {
02616 continue;
02617 }
02618 }
02619
02620 if ( flags&CP_HORZ_DIST_COLL )
02621 {
02622 distance = DistanceHorizontalSquared( origin, level.combatPoints[i].origin );
02623 }
02624 else
02625 {
02626 distance = DistanceSquared( origin, level.combatPoints[i].origin );
02627 }
02628
02629 if ( distance < radiusSqr )
02630 {
02631 if (distance < bestDistance)
02632 {
02633 bestDistance = distance;
02634 bestPoint = numPoints;
02635 }
02636
02637 points[numPoints].dist = distance;
02638 points[numPoints].index = i;
02639 numPoints++;
02640 }
02641 }
02642
02643 return numPoints;
02644 }
02645
02646
02647
02648
02649
02650
02651
02652 #define MIN_AVOID_DOT 0.75f
02653 #define MIN_AVOID_DISTANCE 128
02654 #define MIN_AVOID_DISTANCE_SQUARED ( MIN_AVOID_DISTANCE * MIN_AVOID_DISTANCE )
02655 #define CP_COLLECT_RADIUS 512.0f
02656
02657 int NPC_FindCombatPoint( const vec3_t position, const vec3_t avoidPosition, vec3_t enemyPosition, const int flags, const float avoidDist, const int ignorePoint )
02658 {
02659 combatPt_t points[MAX_COMBAT_POINTS];
02660 int best = -1, cost, bestCost = Q3_INFINITE, waypoint = WAYPOINT_NONE;
02661 float dist;
02662 trace_t tr;
02663 float collRad = CP_COLLECT_RADIUS;
02664 int numPoints;
02665 int j = 0;
02666 float modifiedAvoidDist = avoidDist;
02667
02668 if ( modifiedAvoidDist <= 0 )
02669 {
02670 modifiedAvoidDist = MIN_AVOID_DISTANCE_SQUARED;
02671 }
02672 else
02673 {
02674 modifiedAvoidDist *= modifiedAvoidDist;
02675 }
02676
02677 if ( (flags & CP_HAS_ROUTE) || (flags & CP_NEAREST) )
02678 {
02679 if ( NPC->waypoint == WAYPOINT_NONE )
02680 {
02681 waypoint = NAV_GetNearestNode( NPC, NPC->lastWaypoint );
02682 }
02683 else
02684 {
02685 waypoint = NPC->waypoint;
02686 }
02687 }
02688
02689
02690 if ( flags & CP_NO_PVS )
02691 {
02692 collRad = CP_COLLECT_RADIUS*4;
02693 }
02694 numPoints = NPC_CollectCombatPoints( enemyPosition, collRad, points, flags );
02695
02696
02697 for ( j = 0; j < numPoints; j++ )
02698 {
02699
02700 const int i = points[j].index;
02701 const float pdist = points[j].dist;
02702
02703
02704 if ( i == ignorePoint )
02705 continue;
02706
02707
02708
02709 if ( ( flags & CP_COVER ) && ( NPC_ClearLOS( level.combatPoints[i].origin, enemyPosition ) == qtrue ) )
02710 continue;
02711
02712
02713 if ( flags & CP_CLEAR )
02714 {
02715 if ( NPC_ClearLOS3( level.combatPoints[i].origin, NPC->enemy ) == qfalse )
02716 {
02717 continue;
02718 }
02719 if ( NPC->s.weapon == WP_THERMAL )
02720 {
02721 dist = DistanceHorizontalSquared( level.combatPoints[i].origin, NPC->enemy->r.currentOrigin );
02722 }
02723 else
02724 {
02725 dist = DistanceSquared( level.combatPoints[i].origin, NPC->enemy->r.currentOrigin );
02726 }
02727 if ( dist > (NPCInfo->stats.visrange*NPCInfo->stats.visrange) )
02728 {
02729 continue;
02730 }
02731 }
02732
02733
02734 if ( ( flags & CP_AVOID ) && ( DistanceSquared( level.combatPoints[i].origin, position ) < modifiedAvoidDist ) )
02735 continue;
02736
02737
02738 if ( flags & CP_APPROACH_ENEMY )
02739 {
02740 if ( flags&CP_HORZ_DIST_COLL )
02741 {
02742 if ( pdist > DistanceHorizontalSquared( position, enemyPosition ) )
02743 {
02744 continue;
02745 }
02746 }
02747 else
02748 {
02749 if ( pdist > DistanceSquared( position, enemyPosition ) )
02750 {
02751 continue;
02752 }
02753 }
02754 }
02755
02756 if ( flags & CP_RETREAT )
02757 {
02758 if ( flags&CP_HORZ_DIST_COLL )
02759 {
02760 if ( pdist < DistanceHorizontalSquared( position, enemyPosition ) )
02761 {
02762 continue;
02763 }
02764 }
02765 else
02766 {
02767 if ( pdist < DistanceSquared( position, enemyPosition ) )
02768 {
02769 continue;
02770 }
02771 }
02772 }
02773
02774
02775 if ( flags & CP_FLANK )
02776 {
02777 vec3_t eDir2Me, eDir2CP;
02778 float dot;
02779
02780 VectorSubtract( position, enemyPosition, eDir2Me );
02781 VectorNormalize( eDir2Me );
02782
02783 VectorSubtract( level.combatPoints[i].origin, enemyPosition, eDir2CP );
02784 VectorNormalize( eDir2CP );
02785
02786 dot = DotProduct( eDir2Me, eDir2CP );
02787
02788
02789 if ( dot >= 0.4 )
02790 continue;
02791 }
02792
02793
02794
02795 if ( flags & CP_AVOID_ENEMY )
02796 {
02797 vec3_t eDir, gDir;
02798 vec3_t wpOrg;
02799 float dot;
02800
02801 VectorSubtract( position, enemyPosition, eDir );
02802 VectorNormalize( eDir );
02803
02804
02805
02806
02807
02808
02809
02810
02811
02812 {
02813 VectorCopy( level.combatPoints[i].origin, wpOrg );
02814 }
02815 VectorSubtract( position, wpOrg, gDir );
02816 VectorNormalize( gDir );
02817
02818 dot = DotProduct( gDir, eDir );
02819
02820
02821 if ( dot >= MIN_AVOID_DOT )
02822 continue;
02823
02824
02825 if ( DistanceSquared( wpOrg, enemyPosition ) < modifiedAvoidDist )
02826 continue;
02827 }
02828
02829
02830 trap_Trace( &tr, level.combatPoints[i].origin, NPC->r.mins, NPC->r.maxs, level.combatPoints[i].origin, NPC->s.number, NPC->clipmask );
02831 if ( tr.allsolid || tr.startsolid )
02832 {
02833 continue;
02834 }
02835
02836
02837 if ( flags & CP_HAS_ROUTE )
02838 {
02839
02840
02841
02842
02843
02844
02845
02846 if ( waypoint == WAYPOINT_NONE || level.combatPoints[i].waypoint == WAYPOINT_NONE || trap_Nav_GetBestNodeAltRoute2( waypoint, level.combatPoints[i].waypoint, NODE_NONE ) == WAYPOINT_NONE )
02847 {
02848 if ( !NAV_ClearPathToPoint( NPC, NPC->r.mins, NPC->r.maxs, level.combatPoints[i].origin, NPC->clipmask, ENTITYNUM_NONE ) )
02849 {
02850 continue;
02851 }
02852 }
02853 }
02854
02855
02856 if ( flags & CP_NEAREST && waypoint != WAYPOINT_NONE && level.combatPoints[i].waypoint != WAYPOINT_NONE )
02857 {
02858 cost = trap_Nav_GetPathCost( waypoint, level.combatPoints[i].waypoint );
02859 if ( cost < bestCost )
02860 {
02861 bestCost = cost;
02862 best = i;
02863 }
02864 continue;
02865 }
02866
02867
02868
02869
02870 return i;
02871 }
02872
02873 return best;
02874 }
02875
02876
02877
02878
02879
02880
02881
02882 int NPC_FindSquadPoint( vec3_t position )
02883 {
02884 float dist, nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE;
02885 int nearestPoint = -1;
02886 int i;
02887
02888
02889
02890 for ( i = 0; i < level.numCombatPoints; i++ )
02891 {
02892
02893 if ( ( level.combatPoints[i].flags & CPF_SQUAD ) == qfalse )
02894 continue;
02895
02896
02897 if ( level.combatPoints[i].occupied == qtrue )
02898 continue;
02899
02900 dist = DistanceSquared( position, level.combatPoints[i].origin );
02901
02902
02903
02904
02905
02906
02907 if ( dist < nearestDist )
02908 {
02909 nearestPoint = i;
02910 nearestDist = dist;
02911 }
02912 }
02913
02914 return nearestPoint;
02915 }
02916
02917
02918
02919
02920
02921
02922
02923 qboolean NPC_ReserveCombatPoint( int combatPointID )
02924 {
02925
02926 if ( combatPointID > level.numCombatPoints )
02927 return qfalse;
02928
02929
02930 if ( level.combatPoints[combatPointID].occupied )
02931 return qfalse;
02932
02933
02934 level.combatPoints[combatPointID].occupied = qtrue;
02935
02936 return qtrue;
02937 }
02938
02939
02940
02941
02942
02943
02944
02945 qboolean NPC_FreeCombatPoint( int combatPointID, qboolean failed )
02946 {
02947 if ( failed )
02948 {
02949 NPCInfo->lastFailedCombatPoint = combatPointID;
02950 }
02951
02952 if ( combatPointID > level.numCombatPoints )
02953 return qfalse;
02954
02955
02956 if ( level.combatPoints[combatPointID].occupied == qfalse )
02957 return qfalse;
02958
02959
02960 level.combatPoints[combatPointID].occupied = qfalse;
02961
02962 return qtrue;
02963 }
02964
02965
02966
02967
02968
02969
02970
02971 qboolean NPC_SetCombatPoint( int combatPointID )
02972 {
02973
02974 if ( NPCInfo->combatPoint != -1 )
02975 {
02976 NPC_FreeCombatPoint( NPCInfo->combatPoint, qfalse );
02977 }
02978
02979 if ( NPC_ReserveCombatPoint( combatPointID ) == qfalse )
02980 return qfalse;
02981
02982 NPCInfo->combatPoint = combatPointID;
02983
02984 return qtrue;
02985 }
02986
02987 extern qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper );
02988 gentity_t *NPC_SearchForWeapons( void )
02989 {
02990 gentity_t *found = g_entities, *bestFound = NULL;
02991 float dist, bestDist = Q3_INFINITE;
02992 int i;
02993
02994 for ( i = 0; i<level.num_entities; i++)
02995 {
02996
02997
02998
02999
03000 if(!g_entities[i].inuse)
03001 continue;
03002
03003 found=&g_entities[i];
03004
03005
03006 if ( found->s.eType != ET_ITEM )
03007 {
03008 continue;
03009 }
03010 if ( found->item->giType != IT_WEAPON )
03011 {
03012 continue;
03013 }
03014 if ( found->s.eFlags & EF_NODRAW )
03015 {
03016 continue;
03017 }
03018 if ( CheckItemCanBePickedUpByNPC( found, NPC ) )
03019 {
03020 if ( trap_InPVS( found->r.currentOrigin, NPC->r.currentOrigin ) )
03021 {
03022 dist = DistanceSquared( found->r.currentOrigin, NPC->r.currentOrigin );
03023 if ( dist < bestDist )
03024 {
03025 if ( !trap_Nav_GetBestPathBetweenEnts( NPC, found, NF_CLEAR_PATH )
03026 || trap_Nav_GetBestNodeAltRoute2( NPC->waypoint, found->waypoint, NODE_NONE ) == WAYPOINT_NONE )
03027 {
03028 if ( NAV_ClearPathToPoint( NPC, NPC->r.mins, NPC->r.maxs, found->r.currentOrigin, NPC->clipmask, ENTITYNUM_NONE ) )
03029 {
03030 bestDist = dist;
03031 bestFound = found;
03032 }
03033 }
03034 else
03035 {
03036 bestDist = dist;
03037 bestFound = found;
03038 }
03039 }
03040 }
03041 }
03042 }
03043
03044 return bestFound;
03045 }
03046
03047 void NPC_SetPickUpGoal( gentity_t *foundWeap )
03048 {
03049 vec3_t org;
03050
03051
03052 VectorCopy( foundWeap->r.currentOrigin, org );
03053 org[2] += 24 - (foundWeap->r.mins[2]*-1);
03054 NPC_SetMoveGoal( NPC, org, foundWeap->r.maxs[0]*0.75, qfalse, -1, foundWeap );
03055 NPCInfo->tempGoal->waypoint = foundWeap->waypoint;
03056 NPCInfo->tempBehavior = BS_DEFAULT;
03057 NPCInfo->squadState = SQUAD_TRANSITION;
03058 }
03059
03060 void NPC_CheckGetNewWeapon( void )
03061 {
03062 if ( NPC->s.weapon == WP_NONE && NPC->enemy )
03063 {
03064 if ( NPCInfo->goalEntity
03065 && NPCInfo->goalEntity == NPCInfo->tempGoal
03066 && NPCInfo->goalEntity->enemy
03067 && !NPCInfo->goalEntity->enemy->inuse )
03068 {
03069 NPCInfo->goalEntity = NULL;
03070 }
03071 if ( TIMER_Done( NPC, "panic" ) && NPCInfo->goalEntity == NULL )
03072 {
03073 gentity_t *foundWeap = NPC_SearchForWeapons();
03074 if ( foundWeap )
03075 {
03076
03077
03078
03079
03080
03081
03082
03083
03084
03085
03086
03087
03088
03089
03090 {
03091 NPC_SetPickUpGoal( foundWeap );
03092 }
03093 }
03094 }
03095 }
03096 }
03097
03098 void NPC_AimAdjust( int change )
03099 {
03100 if ( !TIMER_Exists( NPC, "aimDebounce" ) )
03101 {
03102 int debounce = 500+(3-g_spskill.integer)*100;
03103 TIMER_Set( NPC, "aimDebounce", Q_irand( debounce,debounce+1000 ) );
03104
03105
03106 return;
03107 }
03108 if ( TIMER_Done( NPC, "aimDebounce" ) )
03109 {
03110 int debounce;
03111
03112 NPCInfo->currentAim += change;
03113 if ( NPCInfo->currentAim > NPCInfo->stats.aim )
03114 {
03115 NPCInfo->currentAim = NPCInfo->stats.aim;
03116 }
03117 else if ( NPCInfo->currentAim < -30 )
03118 {
03119 NPCInfo->currentAim = -30;
03120 }
03121
03122
03123
03124 debounce = 500+(3-g_spskill.integer)*100;
03125 TIMER_Set( NPC, "aimDebounce", Q_irand( debounce,debounce+1000 ) );
03126
03127
03128 }
03129 }
03130
03131 void G_AimSet( gentity_t *self, int aim )
03132 {
03133 if ( self->NPC )
03134 {
03135 int debounce;
03136
03137 self->NPC->currentAim = aim;
03138
03139
03140 debounce = 500+(3-g_spskill.integer)*100;
03141 TIMER_Set( self, "aimDebounce", Q_irand( debounce,debounce+1000 ) );
03142
03143
03144 }
03145 }