AI_Tusken.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. // leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
  2. #include "g_headers.h"
  3. #include "b_local.h"
  4. #include "g_nav.h"
  5. #include "anims.h"
  6. #include "g_navigator.h"
  7. extern void CG_DrawAlert( vec3_t origin, float rating );
  8. extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
  9. extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
  10. extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
  11. extern void NPC_AimAdjust( int change );
  12. extern qboolean FlyingCreature( gentity_t *ent );
  13. extern int PM_AnimLength( int index, animNumber_t anim );
  14. #define MAX_VIEW_DIST 1024
  15. #define MAX_VIEW_SPEED 250
  16. #define MAX_LIGHT_INTENSITY 255
  17. #define MIN_LIGHT_THRESHOLD 0.1
  18. #define DISTANCE_SCALE 0.25f
  19. #define DISTANCE_THRESHOLD 0.075f
  20. #define SPEED_SCALE 0.25f
  21. #define FOV_SCALE 0.5f
  22. #define LIGHT_SCALE 0.25f
  23. #define REALIZE_THRESHOLD 0.6f
  24. #define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 )
  25. qboolean NPC_CheckPlayerTeamStealth( void );
  26. static qboolean enemyLOS;
  27. static qboolean enemyCS;
  28. static qboolean faceEnemy;
  29. static qboolean move;
  30. static qboolean shoot;
  31. static float enemyDist;
  32. //Local state enums
  33. enum
  34. {
  35. LSTATE_NONE = 0,
  36. LSTATE_UNDERFIRE,
  37. LSTATE_INVESTIGATE,
  38. };
  39. /*
  40. -------------------------
  41. NPC_Tusken_Precache
  42. -------------------------
  43. */
  44. void NPC_Tusken_Precache( void )
  45. {
  46. int i;
  47. for ( i = 1; i < 5; i ++ )
  48. {
  49. G_SoundIndex( va( "sound/weapons/tusken_staff/stickhit%d.wav", i ) );
  50. }
  51. }
  52. void Tusken_ClearTimers( gentity_t *ent )
  53. {
  54. TIMER_Set( ent, "chatter", 0 );
  55. TIMER_Set( ent, "duck", 0 );
  56. TIMER_Set( ent, "stand", 0 );
  57. TIMER_Set( ent, "shuffleTime", 0 );
  58. TIMER_Set( ent, "sleepTime", 0 );
  59. TIMER_Set( ent, "enemyLastVisible", 0 );
  60. TIMER_Set( ent, "roamTime", 0 );
  61. TIMER_Set( ent, "hideTime", 0 );
  62. TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels
  63. TIMER_Set( ent, "stick", 0 );
  64. TIMER_Set( ent, "scoutTime", 0 );
  65. TIMER_Set( ent, "flee", 0 );
  66. TIMER_Set( ent, "taunting", 0 );
  67. }
  68. void NPC_Tusken_PlayConfusionSound( gentity_t *self )
  69. {//FIXME: make this a custom sound in sound set
  70. if ( self->health > 0 )
  71. {
  72. G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 );
  73. }
  74. //reset him to be totally unaware again
  75. TIMER_Set( self, "enemyLastVisible", 0 );
  76. TIMER_Set( self, "flee", 0 );
  77. self->NPC->squadState = SQUAD_IDLE;
  78. self->NPC->tempBehavior = BS_DEFAULT;
  79. //self->NPC->behaviorState = BS_PATROL;
  80. G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;?
  81. self->NPC->investigateCount = 0;
  82. }
  83. /*
  84. -------------------------
  85. NPC_ST_Pain
  86. -------------------------
  87. */
  88. void NPC_Tusken_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod )
  89. {
  90. self->NPC->localState = LSTATE_UNDERFIRE;
  91. TIMER_Set( self, "duck", -1 );
  92. TIMER_Set( self, "stand", 2000 );
  93. NPC_Pain( self, inflictor, other, point, damage, mod );
  94. if ( !damage && self->health > 0 )
  95. {//FIXME: better way to know I was pushed
  96. G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
  97. }
  98. }
  99. /*
  100. -------------------------
  101. ST_HoldPosition
  102. -------------------------
  103. */
  104. static void Tusken_HoldPosition( void )
  105. {
  106. NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue );
  107. NPCInfo->goalEntity = NULL;
  108. }
  109. /*
  110. -------------------------
  111. ST_Move
  112. -------------------------
  113. */
  114. static qboolean Tusken_Move( void )
  115. {
  116. NPCInfo->combatMove = qtrue;//always move straight toward our goal
  117. qboolean moved = NPC_MoveToGoal( qtrue );
  118. //If our move failed, then reset
  119. if ( moved == qfalse )
  120. {//couldn't get to enemy
  121. //just hang here
  122. Tusken_HoldPosition();
  123. }
  124. return moved;
  125. }
  126. /*
  127. -------------------------
  128. NPC_BSTusken_Patrol
  129. -------------------------
  130. */
  131. void NPC_BSTusken_Patrol( void )
  132. {//FIXME: pick up on bodies of dead buddies?
  133. if ( NPCInfo->confusionTime < level.time )
  134. {
  135. //Look for any enemies
  136. if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
  137. {
  138. if ( NPC_CheckPlayerTeamStealth() )
  139. {
  140. //NPC_AngerSound();
  141. NPC_UpdateAngles( qtrue, qtrue );
  142. return;
  143. }
  144. }
  145. if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
  146. {
  147. //Is there danger nearby
  148. int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS );
  149. if ( NPC_CheckForDanger( alertEvent ) )
  150. {
  151. NPC_UpdateAngles( qtrue, qtrue );
  152. return;
  153. }
  154. else
  155. {//check for other alert events
  156. //There is an event to look at
  157. if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
  158. {
  159. //NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID;
  160. if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED )
  161. {
  162. if ( level.alertEvents[alertEvent].owner &&
  163. level.alertEvents[alertEvent].owner->client &&
  164. level.alertEvents[alertEvent].owner->health >= 0 &&
  165. level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam )
  166. {//an enemy
  167. G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
  168. //NPCInfo->enemyLastSeenTime = level.time;
  169. TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
  170. }
  171. }
  172. else
  173. {//FIXME: get more suspicious over time?
  174. //Save the position for movement (if necessary)
  175. VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal );
  176. NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 );
  177. if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS )
  178. {//suspicious looks longer
  179. NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 );
  180. }
  181. }
  182. }
  183. }
  184. if ( NPCInfo->investigateDebounceTime > level.time )
  185. {//FIXME: walk over to it, maybe? Not if not chase enemies
  186. //NOTE: stops walking or doing anything else below
  187. vec3_t dir, angles;
  188. float o_yaw, o_pitch;
  189. VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir );
  190. vectoangles( dir, angles );
  191. o_yaw = NPCInfo->desiredYaw;
  192. o_pitch = NPCInfo->desiredPitch;
  193. NPCInfo->desiredYaw = angles[YAW];
  194. NPCInfo->desiredPitch = angles[PITCH];
  195. NPC_UpdateAngles( qtrue, qtrue );
  196. NPCInfo->desiredYaw = o_yaw;
  197. NPCInfo->desiredPitch = o_pitch;
  198. return;
  199. }
  200. }
  201. }
  202. //If we have somewhere to go, then do that
  203. if ( UpdateGoal() )
  204. {
  205. ucmd.buttons |= BUTTON_WALKING;
  206. NPC_MoveToGoal( qtrue );
  207. }
  208. NPC_UpdateAngles( qtrue, qtrue );
  209. }
  210. void NPC_Tusken_Taunt( void )
  211. {
  212. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TUSKENTAUNT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  213. TIMER_Set( NPC, "taunting", NPC->client->ps.torsoAnimTimer );
  214. TIMER_Set( NPC, "duck", -1 );
  215. }
  216. /*
  217. -------------------------
  218. NPC_BSTusken_Attack
  219. -------------------------
  220. */
  221. void NPC_BSTusken_Attack( void )
  222. {
  223. // IN PAIN
  224. //---------
  225. if ( NPC->painDebounceTime > level.time )
  226. {
  227. NPC_UpdateAngles( qtrue, qtrue );
  228. return;
  229. }
  230. // IN FLEE
  231. //---------
  232. if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
  233. {
  234. NPC_UpdateAngles( qtrue, qtrue );
  235. return;
  236. }
  237. // UPDATE OUR ENEMY
  238. //------------------
  239. if (NPC_CheckEnemyExt()==qfalse || !NPC->enemy)
  240. {
  241. NPC_BSTusken_Patrol();
  242. return;
  243. }
  244. enemyDist = Distance(NPC->enemy->currentOrigin, NPC->currentOrigin);
  245. // Is The Current Enemy A Jawa?
  246. //------------------------------
  247. if (NPC->enemy->client && NPC->enemy->client->NPC_class==CLASS_JAWA)
  248. {
  249. // Make Sure His Enemy Is Me
  250. //---------------------------
  251. if (NPC->enemy->enemy!=NPC)
  252. {
  253. G_SetEnemy(NPC->enemy, NPC);
  254. }
  255. // Should We Forget About Our Current Enemy And Go After The Player?
  256. //-------------------------------------------------------------------
  257. if ((player) && // If There Is A Player Pointer
  258. (player!=NPC->enemy) && // The Player Is Not Currently My Enemy
  259. (Distance(player->currentOrigin, NPC->currentOrigin)<130.0f) && // The Player Is Close Enough
  260. (NAV::InSameRegion(NPC, player)) // And In The Same Region
  261. )
  262. {
  263. G_SetEnemy(NPC, player);
  264. }
  265. }
  266. // Update Our Last Seen Time
  267. //---------------------------
  268. if (NPC_ClearLOS(NPC->enemy))
  269. {
  270. NPCInfo->enemyLastSeenTime = level.time;
  271. }
  272. // Check To See If We Are In Attack Range
  273. //----------------------------------------
  274. float boundsMin = (NPC->maxs[0]+NPC->enemy->maxs[0]);
  275. float lungeRange = (boundsMin + 65.0f);
  276. float strikeRange = (boundsMin + 40.0f);
  277. bool meleeRange = (enemyDist<lungeRange);
  278. bool meleeWeapon = (NPC->client->ps.weapon!=WP_TUSKEN_RIFLE);
  279. bool canSeeEnemy = ((level.time - NPCInfo->enemyLastSeenTime)<3000);
  280. // Check To Start Taunting
  281. //-------------------------
  282. if (canSeeEnemy && !meleeRange && TIMER_Done(NPC, "tuskenTauntCheck"))
  283. {
  284. TIMER_Set(NPC, "tuskenTauntCheck", Q_irand(2000, 6000));
  285. if (!Q_irand(0,3))
  286. {
  287. NPC_Tusken_Taunt();
  288. }
  289. }
  290. if (TIMER_Done(NPC, "taunting"))
  291. {
  292. // Should I Attack?
  293. //------------------
  294. if (meleeRange || (!meleeWeapon && canSeeEnemy))
  295. {
  296. if (!(NPCInfo->scriptFlags&SCF_FIRE_WEAPON) && // If This Flag Is On, It Calls Attack From Elsewhere
  297. !(NPCInfo->scriptFlags&SCF_DONT_FIRE) && // If This Flag Is On, Don't Fire At All
  298. (TIMER_Done(NPC, "attackDelay"))
  299. )
  300. {
  301. ucmd.buttons &= ~BUTTON_ALT_ATTACK;
  302. // If Not In Strike Range, Do Lunge, Or If We Don't Have The Staff, Just Shoot Normally
  303. //--------------------------------------------------------------------------------------
  304. if (enemyDist > strikeRange)
  305. {
  306. ucmd.buttons |= BUTTON_ALT_ATTACK;
  307. }
  308. WeaponThink( qtrue );
  309. TIMER_Set(NPC, "attackDelay", NPCInfo->shotTime-level.time);
  310. }
  311. if ( !TIMER_Done( NPC, "duck" ) )
  312. {
  313. ucmd.upmove = -127;
  314. }
  315. }
  316. // Or Should I Move?
  317. //-------------------
  318. else if (NPCInfo->scriptFlags & SCF_CHASE_ENEMIES)
  319. {
  320. NPCInfo->goalEntity = NPC->enemy;
  321. NPCInfo->goalRadius = lungeRange;
  322. Tusken_Move();
  323. }
  324. }
  325. // UPDATE ANGLES
  326. //---------------
  327. if (canSeeEnemy)
  328. {
  329. NPC_FaceEnemy(qtrue);
  330. }
  331. NPC_UpdateAngles(qtrue, qtrue);
  332. }
  333. extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
  334. void Tusken_StaffTrace( void )
  335. {
  336. if ( !NPC->ghoul2.size()
  337. || NPC->weaponModel[0] <= 0 )
  338. {
  339. return;
  340. }
  341. int boltIndex = gi.G2API_AddBolt(&NPC->ghoul2[NPC->weaponModel[0]], "*weapon");
  342. if ( boltIndex != -1 )
  343. {
  344. int curTime = (cg.time?cg.time:level.time);
  345. qboolean hit = qfalse;
  346. int lastHit = ENTITYNUM_NONE;
  347. for ( int time = curTime-25; time <= curTime+25&&!hit; time += 25 )
  348. {
  349. mdxaBone_t boltMatrix;
  350. vec3_t tip, dir, base, angles={0,NPC->currentAngles[YAW],0};
  351. vec3_t mins={-2,-2,-2},maxs={2,2,2};
  352. trace_t trace;
  353. gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->weaponModel[0],
  354. boltIndex,
  355. &boltMatrix, angles, NPC->currentOrigin, time,
  356. NULL, NPC->s.modelScale );
  357. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, base );
  358. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir );
  359. VectorMA( base, -20, dir, base );
  360. VectorMA( base, 78, dir, tip );
  361. #ifndef FINAL_BUILD
  362. if ( d_saberCombat->integer > 1 )
  363. {
  364. G_DebugLine(base, tip, 1000, 0x000000ff, qtrue);
  365. }
  366. #endif
  367. gi.trace( &trace, base, mins, maxs, tip, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
  368. if ( trace.fraction < 1.0f && trace.entityNum != lastHit )
  369. {//hit something
  370. gentity_t *traceEnt = &g_entities[trace.entityNum];
  371. if ( traceEnt->takedamage
  372. && (!traceEnt->client || traceEnt == NPC->enemy || traceEnt->client->NPC_class != NPC->client->NPC_class) )
  373. {//smack
  374. int dmg = Q_irand( 5, 10 ) * (g_spskill->integer+1);
  375. //FIXME: debounce?
  376. G_Sound( traceEnt, G_SoundIndex( va( "sound/weapons/tusken_staff/stickhit%d.wav", Q_irand( 1, 4 ) ) ) );
  377. G_Damage( traceEnt, NPC, NPC, vec3_origin, trace.endpos, dmg, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
  378. if ( traceEnt->health > 0
  379. && ( (traceEnt->client&&traceEnt->client->NPC_class==CLASS_JAWA&&!Q_irand(0,1))
  380. || dmg > 19 ) )//FIXME: base on skill!
  381. {//do pain on enemy
  382. G_Knockdown( traceEnt, NPC, dir, 300, qtrue );
  383. }
  384. lastHit = trace.entityNum;
  385. hit = qtrue;
  386. }
  387. }
  388. }
  389. }
  390. }
  391. qboolean G_TuskenAttackAnimDamage( gentity_t *self )
  392. {
  393. if (self->client->ps.torsoAnim==BOTH_TUSKENATTACK1 ||
  394. self->client->ps.torsoAnim==BOTH_TUSKENATTACK2 ||
  395. self->client->ps.torsoAnim==BOTH_TUSKENATTACK3 ||
  396. self->client->ps.torsoAnim==BOTH_TUSKENLUNGE1)
  397. {
  398. float current = 0.0f;
  399. int end = 0;
  400. int start = 0;
  401. if (!!gi.G2API_GetBoneAnimIndex(&
  402. self->ghoul2[self->playerModel],
  403. self->lowerLumbarBone,
  404. level.time,
  405. &current,
  406. &start,
  407. &end,
  408. NULL,
  409. NULL,
  410. NULL))
  411. {
  412. float percentComplete = (current-start)/(end-start);
  413. //gi.Printf("%f\n", percentComplete);
  414. switch (self->client->ps.torsoAnim)
  415. {
  416. case BOTH_TUSKENATTACK1: return (percentComplete>0.3 && percentComplete<0.7);
  417. case BOTH_TUSKENATTACK2: return (percentComplete>0.3 && percentComplete<0.7);
  418. case BOTH_TUSKENATTACK3: return (percentComplete>0.1 && percentComplete<0.5);
  419. case BOTH_TUSKENLUNGE1: return (percentComplete>0.3 && percentComplete<0.5);
  420. }
  421. }
  422. }
  423. return qfalse;
  424. }
  425. void NPC_BSTusken_Default( void )
  426. {
  427. if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
  428. {
  429. WeaponThink( qtrue );
  430. }
  431. if ( G_TuskenAttackAnimDamage( NPC ) )
  432. {
  433. Tusken_StaffTrace();
  434. }
  435. if( !NPC->enemy )
  436. {//don't have an enemy, look for one
  437. NPC_BSTusken_Patrol();
  438. }
  439. else//if ( NPC->enemy )
  440. {//have an enemy
  441. NPC_BSTusken_Attack();
  442. }
  443. }