AI_SandCreature.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  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. extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
  5. #define MIN_ATTACK_DIST_SQ 128
  6. #define MIN_MISS_DIST 100
  7. #define MIN_MISS_DIST_SQ (MIN_MISS_DIST*MIN_MISS_DIST)
  8. #define MAX_MISS_DIST 500
  9. #define MAX_MISS_DIST_SQ (MAX_MISS_DIST*MAX_MISS_DIST)
  10. #define MIN_SCORE -37500 //speed of (50*50) - dist of (200*200)
  11. void SandCreature_Precache( void )
  12. {
  13. int i;
  14. G_EffectIndex( "env/sand_dive" );
  15. G_EffectIndex( "env/sand_spray" );
  16. G_EffectIndex( "env/sand_move" );
  17. G_EffectIndex( "env/sand_move_breach" );
  18. //G_EffectIndex( "env/sand_attack_breach" );
  19. for ( i = 1; i < 4; i++ )
  20. {
  21. G_SoundIndex( va( "sound/chars/sand_creature/voice%d.mp3", i ) );
  22. }
  23. G_SoundIndex( "sound/chars/sand_creature/slither.wav" );
  24. }
  25. void SandCreature_ClearTimers( gentity_t *ent )
  26. {
  27. TIMER_Set( NPC, "speaking", -level.time );
  28. TIMER_Set( NPC, "breaching", -level.time );
  29. TIMER_Set( NPC, "breachDebounce", -level.time );
  30. TIMER_Set( NPC, "pain", -level.time );
  31. TIMER_Set( NPC, "attacking", -level.time );
  32. TIMER_Set( NPC, "missDebounce", -level.time );
  33. }
  34. void NPC_SandCreature_Die( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
  35. {
  36. //FIXME: somehow make him solid when he dies?
  37. }
  38. void NPC_SandCreature_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
  39. {
  40. if ( TIMER_Done( self, "pain" ) )
  41. {
  42. //FIXME: effect and sound
  43. //FIXME: shootable during this anim?
  44. NPC_SetAnim( self, SETANIM_LEGS, Q_irand(BOTH_ATTACK1,BOTH_ATTACK2), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
  45. G_AddEvent( self, EV_PAIN, Q_irand( 0, 100 ) );
  46. TIMER_Set( self, "pain", self->client->ps.legsAnimTimer + Q_irand( 500, 2000 ) );
  47. float playerDist = Distance( player->currentOrigin, self->currentOrigin );
  48. if ( playerDist < 256 )
  49. {
  50. CGCam_Shake( 1.0f*playerDist/128.0f, self->client->ps.legsAnimTimer );
  51. }
  52. }
  53. self->enemy = self->NPC->goalEntity = NULL;
  54. }
  55. void SandCreature_MoveEffect( void )
  56. {
  57. vec3_t up = {0,0,1};
  58. vec3_t org = {NPC->currentOrigin[0], NPC->currentOrigin[1], NPC->absmin[2]+2};
  59. float playerDist = Distance( player->currentOrigin, NPC->currentOrigin );
  60. if ( playerDist < 256 )
  61. {
  62. CGCam_Shake( 0.75f*playerDist/256.0f, 250 );
  63. }
  64. if ( level.time-NPC->client->ps.lastStationary > 2000 )
  65. {//first time moving for at least 2 seconds
  66. //clear speakingtime
  67. TIMER_Set( NPC, "speaking", -level.time );
  68. }
  69. if ( TIMER_Done( NPC, "breaching" )
  70. && TIMER_Done( NPC, "breachDebounce" )
  71. && TIMER_Done( NPC, "pain" )
  72. && TIMER_Done( NPC, "attacking" )
  73. && !Q_irand( 0, 10 ) )
  74. {//Breach!
  75. //FIXME: only do this while moving forward?
  76. trace_t trace;
  77. //make him solid here so he can be hit/gets blocked on stuff. Check clear first.
  78. gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, NPC->currentOrigin, NPC->s.number, MASK_NPCSOLID );
  79. if ( !trace.allsolid && !trace.startsolid )
  80. {
  81. NPC->clipmask = MASK_NPCSOLID;//turn solid for a little bit
  82. NPC->contents = CONTENTS_BODY;
  83. //NPC->takedamage = qtrue;//can be shot?
  84. //FIXME: Breach sound?
  85. //FIXME: Breach effect?
  86. NPC_SetAnim( NPC, SETANIM_LEGS, BOTH_WALK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
  87. TIMER_Set( NPC, "breaching", NPC->client->ps.legsAnimTimer );
  88. TIMER_Set( NPC, "breachDebounce", NPC->client->ps.legsAnimTimer+Q_irand( 0, 10000 ) );
  89. }
  90. }
  91. if ( !TIMER_Done( NPC, "breaching" ) )
  92. {//different effect when breaching
  93. //FIXME: make effect
  94. G_PlayEffect( G_EffectIndex( "env/sand_move_breach" ), org, up );
  95. }
  96. else
  97. {
  98. G_PlayEffect( G_EffectIndex( "env/sand_move" ), org, up );
  99. }
  100. NPC->s.loopSound = G_SoundIndex( "sound/chars/sand_creature/slither.wav" );
  101. }
  102. qboolean SandCreature_CheckAhead( vec3_t end )
  103. {
  104. trace_t trace;
  105. int clipmask = NPC->clipmask|CONTENTS_BOTCLIP;
  106. //make sure our goal isn't underground (else the trace will fail)
  107. vec3_t bottom = {end[0],end[1],end[2]+NPC->mins[2]};
  108. gi.trace( &trace, end, vec3_origin, vec3_origin, bottom, NPC->s.number, NPC->clipmask );
  109. if ( trace.fraction < 1.0f )
  110. {//in the ground, raise it up
  111. end[2] -= NPC->mins[2]*(1.0f-trace.fraction)-0.125f;
  112. }
  113. gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask );
  114. if ( trace.startsolid&&(trace.contents&CONTENTS_BOTCLIP) )
  115. {//started inside do not enter, so ignore them
  116. clipmask &= ~CONTENTS_BOTCLIP;
  117. gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask );
  118. }
  119. //Do a simple check
  120. if ( ( trace.allsolid == qfalse ) && ( trace.startsolid == qfalse ) && ( trace.fraction == 1.0f ) )
  121. return qtrue;
  122. if ( trace.plane.normal[2] >= MIN_WALK_NORMAL )
  123. {
  124. return qtrue;
  125. }
  126. //This is a work around
  127. float radius = ( NPC->maxs[0] > NPC->maxs[1] ) ? NPC->maxs[0] : NPC->maxs[1];
  128. float dist = Distance( NPC->currentOrigin, end );
  129. float tFrac = 1.0f - ( radius / dist );
  130. if ( trace.fraction >= tFrac )
  131. return qtrue;
  132. return qfalse;
  133. }
  134. qboolean SandCreature_Move( void )
  135. {
  136. qboolean moved = qfalse;
  137. //FIXME should ignore doors..?
  138. vec3_t dest;
  139. VectorCopy( NPCInfo->goalEntity->currentOrigin, dest );
  140. //Sand Creatures look silly using waypoints when they can go straight to the goal
  141. if ( SandCreature_CheckAhead( dest ) )
  142. {//use our temp move straight to goal check
  143. VectorSubtract( dest, NPC->currentOrigin, NPC->client->ps.moveDir );
  144. NPC->client->ps.speed = VectorNormalize( NPC->client->ps.moveDir );
  145. if ( (ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed > NPCInfo->stats.walkSpeed )
  146. {
  147. NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
  148. }
  149. else
  150. {
  151. if ( NPC->client->ps.speed < NPCInfo->stats.walkSpeed )
  152. {
  153. NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
  154. }
  155. if ( !(ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed < NPCInfo->stats.runSpeed )
  156. {
  157. NPC->client->ps.speed = NPCInfo->stats.runSpeed;
  158. }
  159. else if ( NPC->client->ps.speed > NPCInfo->stats.runSpeed )
  160. {
  161. NPC->client->ps.speed = NPCInfo->stats.runSpeed;
  162. }
  163. }
  164. moved = qtrue;
  165. }
  166. else
  167. {
  168. moved = NPC_MoveToGoal( qtrue );
  169. }
  170. if ( moved && NPC->radius )
  171. {
  172. vec3_t newPos;
  173. float curTurfRange, newTurfRange;
  174. curTurfRange = DistanceHorizontal( NPC->currentOrigin, NPC->s.origin );
  175. VectorMA( NPC->currentOrigin, NPC->client->ps.speed/100.0f, NPC->client->ps.moveDir, newPos );
  176. newTurfRange = DistanceHorizontal( newPos, NPC->s.origin );
  177. if ( newTurfRange > NPC->radius && newTurfRange > curTurfRange )
  178. {//would leave our range
  179. //stop
  180. NPC->client->ps.speed = 0.0f;
  181. VectorClear( NPC->client->ps.moveDir );
  182. ucmd.forwardmove = ucmd.rightmove = 0;
  183. moved = qfalse;
  184. }
  185. }
  186. return (moved);
  187. //often erroneously returns false ??? something wrong with NAV...?
  188. }
  189. void SandCreature_Attack( qboolean miss )
  190. {
  191. //FIXME: make it able to grab a thermal detonator, take it down,
  192. // then have it explode inside them, killing them
  193. // (or, do damage, making them stick half out of the ground and
  194. // screech for a bit, giving you a chance to run for it!)
  195. //FIXME: effect and sound
  196. //FIXME: shootable during this anim?
  197. if ( !NPC->enemy->client )
  198. {
  199. NPC_SetAnim( NPC, SETANIM_LEGS, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
  200. }
  201. else
  202. {
  203. NPC_SetAnim( NPC, SETANIM_LEGS, Q_irand( BOTH_ATTACK1, BOTH_ATTACK2 ), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
  204. }
  205. //don't do anything else while in this anim
  206. TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer );
  207. float playerDist = Distance( player->currentOrigin, NPC->currentOrigin );
  208. if ( playerDist < 256 )
  209. {
  210. //FIXME: tone this down
  211. CGCam_Shake( 0.75f*playerDist/128.0f, NPC->client->ps.legsAnimTimer );
  212. }
  213. if ( miss )
  214. {//purposely missed him, chance of knocking him down
  215. //FIXME: if, during the attack anim, I do end up catching him close to my mouth, then snatch him anyway...
  216. if ( NPC->enemy && NPC->enemy->client )
  217. {
  218. vec3_t dir2Enemy;
  219. VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, dir2Enemy );
  220. if ( dir2Enemy[2] < 30 )
  221. {
  222. dir2Enemy[2] = 30;
  223. }
  224. if ( g_spskill->integer > 0 )
  225. {
  226. float enemyDist = VectorNormalize( dir2Enemy );
  227. //FIXME: tone this down, smaller radius
  228. if ( enemyDist < 200 && NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
  229. {
  230. float throwStr = ((200-enemyDist)*0.4f)+20;
  231. if ( throwStr > 45 )
  232. {
  233. throwStr = 45;
  234. }
  235. G_Throw( NPC->enemy, dir2Enemy, throwStr );
  236. if ( g_spskill->integer > 1 )
  237. {//knock them down, too
  238. if ( NPC->enemy->health > 0
  239. && Q_flrand( 50, 150 ) > enemyDist )
  240. {//knock them down
  241. G_Knockdown( NPC->enemy, NPC, dir2Enemy, 300, qtrue );
  242. if ( NPC->enemy->s.number < MAX_CLIENTS )
  243. {//make the player look up at me
  244. vec3_t vAng;
  245. vectoangles( dir2Enemy, vAng );
  246. VectorSet( vAng, AngleNormalize180(vAng[PITCH])*-1, NPC->enemy->client->ps.viewangles[YAW], 0 );
  247. SetClientViewAngle( NPC->enemy, vAng );
  248. }
  249. }
  250. }
  251. }
  252. }
  253. }
  254. }
  255. else
  256. {
  257. NPC->enemy->activator = NPC; // kind of dumb, but when we are locked to the Rancor, we are owned by it.
  258. NPC->activator = NPC->enemy;//remember him
  259. //this guy isn't going anywhere anymore
  260. NPC->enemy->contents = 0;
  261. NPC->enemy->clipmask = 0;
  262. if ( NPC->activator->client )
  263. {
  264. NPC->activator->client->ps.SaberDeactivate();
  265. NPC->activator->client->ps.eFlags |= EF_HELD_BY_SAND_CREATURE;
  266. if ( NPC->activator->health > 0 && NPC->activator->client )
  267. {
  268. G_AddEvent( NPC->activator, Q_irand(EV_DEATH1, EV_DEATH3), 0 );
  269. NPC_SetAnim( NPC->activator, SETANIM_LEGS, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  270. NPC_SetAnim( NPC->activator, SETANIM_TORSO, BOTH_FALLDEATH1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  271. TossClientItems( NPC );
  272. if ( NPC->activator->NPC )
  273. {//no more thinking for you
  274. NPC->activator->NPC->nextBStateThink = Q3_INFINITE;
  275. }
  276. }
  277. /*
  278. if ( !NPC->activator->s.number )
  279. {
  280. cg.overrides.active |= (CG_OVERRIDE_3RD_PERSON_CDP|CG_OVERRIDE_3RD_PERSON_RNG);
  281. cg.overrides.thirdPersonCameraDamp = 0;
  282. cg.overrides.thirdPersonRange = 120;
  283. }
  284. */
  285. }
  286. else
  287. {
  288. NPC->activator->s.eFlags |= EF_HELD_BY_SAND_CREATURE;
  289. }
  290. }
  291. }
  292. float SandCreature_EntScore( gentity_t *ent )
  293. {
  294. float moveSpeed, dist;
  295. if ( ent->client )
  296. {
  297. moveSpeed = VectorLengthSquared( ent->client->ps.velocity );
  298. }
  299. else
  300. {
  301. moveSpeed = VectorLengthSquared( ent->s.pos.trDelta );
  302. }
  303. dist = DistanceSquared( NPC->currentOrigin, ent->currentOrigin );
  304. return (moveSpeed-dist);
  305. }
  306. void SandCreature_SeekEnt( gentity_t *bestEnt, float score )
  307. {
  308. NPCInfo->enemyLastSeenTime = level.time;
  309. VectorCopy( bestEnt->currentOrigin, NPCInfo->enemyLastSeenLocation );
  310. NPC_SetMoveGoal( NPC, NPCInfo->enemyLastSeenLocation, 0, qfalse );
  311. if ( score > MIN_SCORE )
  312. {
  313. NPC->enemy = bestEnt;
  314. }
  315. }
  316. void SandCreature_CheckMovingEnts( void )
  317. {
  318. gentity_t *radiusEnts[ 128 ];
  319. int numEnts;
  320. const float radius = NPCInfo->stats.earshot;
  321. int i;
  322. vec3_t mins, maxs;
  323. for ( i = 0; i < 3; i++ )
  324. {
  325. mins[i] = NPC->currentOrigin[i] - radius;
  326. maxs[i] = NPC->currentOrigin[i] + radius;
  327. }
  328. numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, 128 );
  329. int bestEnt = -1;
  330. float bestScore = 0;
  331. float checkScore;
  332. for ( i = 0; i < numEnts; i++ )
  333. {
  334. if ( !radiusEnts[i]->inuse )
  335. {
  336. continue;
  337. }
  338. if ( radiusEnts[i] == NPC )
  339. {//Skip the rancor ent
  340. continue;
  341. }
  342. if ( radiusEnts[i]->client == NULL )
  343. {//must be a client
  344. if ( radiusEnts[i]->s.eType != ET_MISSILE
  345. || radiusEnts[i]->s.weapon != WP_THERMAL )
  346. {//not a thermal detonator
  347. continue;
  348. }
  349. }
  350. else
  351. {
  352. if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) )
  353. {//can't be one being held
  354. continue;
  355. }
  356. if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_WAMPA) )
  357. {//can't be one being held
  358. continue;
  359. }
  360. if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE) )
  361. {//can't be one being held
  362. continue;
  363. }
  364. if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) )
  365. {//not if invisible
  366. continue;
  367. }
  368. if ( radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_WORLD )
  369. {//not on the ground
  370. continue;
  371. }
  372. if ( radiusEnts[i]->client->NPC_class == CLASS_SAND_CREATURE )
  373. {
  374. continue;
  375. }
  376. }
  377. if ( (radiusEnts[i]->flags&FL_NOTARGET) )
  378. {
  379. continue;
  380. }
  381. /*
  382. if ( radiusEnts[i]->client && (radiusEnts[i]->client->NPC_class == CLASS_RANCOR || radiusEnts[i]->client->NPC_class == CLASS_ATST ) )
  383. {//can't grab rancors or atst's
  384. continue;
  385. }
  386. */
  387. checkScore = SandCreature_EntScore( radiusEnts[i] );
  388. //FIXME: take mass into account too? What else?
  389. if ( checkScore > bestScore )
  390. {
  391. bestScore = checkScore;
  392. bestEnt = i;
  393. }
  394. }
  395. if ( bestEnt != -1 )
  396. {
  397. SandCreature_SeekEnt( radiusEnts[bestEnt], bestScore );
  398. }
  399. }
  400. void SandCreature_SeekAlert( int alertEvent )
  401. {
  402. alertEvent_t *alert = &level.alertEvents[alertEvent];
  403. //FIXME: check for higher alert status or closer than last location?
  404. NPCInfo->enemyLastSeenTime = level.time;
  405. VectorCopy( alert->position, NPCInfo->enemyLastSeenLocation );
  406. NPC_SetMoveGoal( NPC, NPCInfo->enemyLastSeenLocation, 0, qfalse );
  407. }
  408. void SandCreature_CheckAlerts( void )
  409. {
  410. if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
  411. {
  412. int alertEvent = NPC_CheckAlertEvents( qfalse, qtrue, NPCInfo->lastAlertID, qfalse, AEL_MINOR, qtrue );
  413. //There is an event to look at
  414. if ( alertEvent >= 0 )
  415. {
  416. //if ( level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
  417. {
  418. SandCreature_SeekAlert( alertEvent );
  419. }
  420. }
  421. }
  422. }
  423. float SandCreature_DistSqToGoal( qboolean goalIsEnemy )
  424. {
  425. float goalDistSq;
  426. if ( !NPCInfo->goalEntity || goalIsEnemy )
  427. {
  428. if ( !NPC->enemy )
  429. {
  430. return Q3_INFINITE;
  431. }
  432. NPCInfo->goalEntity = NPC->enemy;
  433. }
  434. if ( NPCInfo->goalEntity->client )
  435. {
  436. goalDistSq = DistanceSquared( NPC->currentOrigin, NPCInfo->goalEntity->currentOrigin );
  437. }
  438. else
  439. {
  440. vec3_t gOrg;
  441. VectorCopy( NPCInfo->goalEntity->currentOrigin, gOrg );
  442. gOrg[2] -= (NPC->mins[2]-NPCInfo->goalEntity->mins[2]);//moves the gOrg up/down to make it's origin seem at the proper height as if it had my mins
  443. goalDistSq = DistanceSquared( NPC->currentOrigin, gOrg );
  444. }
  445. return goalDistSq;
  446. }
  447. void SandCreature_Chase( void )
  448. {
  449. if ( !NPC->enemy->inuse )
  450. {//freed
  451. NPC->enemy = NULL;
  452. return;
  453. }
  454. if ( (NPC->svFlags&SVF_LOCKEDENEMY) )
  455. {//always know where he is
  456. NPCInfo->enemyLastSeenTime = level.time;
  457. }
  458. if ( !(NPC->svFlags&SVF_LOCKEDENEMY) )
  459. {
  460. if ( level.time-NPCInfo->enemyLastSeenTime > 10000 )
  461. {
  462. NPC->enemy = NULL;
  463. return;
  464. }
  465. }
  466. if ( NPC->enemy->client )
  467. {
  468. if ( (NPC->enemy->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE)
  469. || (NPC->enemy->client->ps.eFlags&EF_HELD_BY_RANCOR)
  470. || (NPC->enemy->client->ps.eFlags&EF_HELD_BY_WAMPA) )
  471. {//was picked up by another monster, forget about him
  472. NPC->enemy = NULL;
  473. NPC->svFlags &= ~SVF_LOCKEDENEMY;
  474. return;
  475. }
  476. }
  477. //chase the enemy
  478. if ( NPC->enemy->client
  479. && NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_WORLD
  480. && !(NPC->svFlags&SVF_LOCKEDENEMY) )
  481. {//off the ground!
  482. //FIXME: keep moving in the dir we were moving for a little bit...
  483. }
  484. else
  485. {
  486. float enemyScore = SandCreature_EntScore( NPC->enemy );
  487. if ( enemyScore < MIN_SCORE
  488. && !(NPC->svFlags&SVF_LOCKEDENEMY) )
  489. {//too slow or too far away
  490. }
  491. else
  492. {
  493. float moveSpeed;
  494. if ( NPC->enemy->client )
  495. {
  496. moveSpeed = VectorLengthSquared( NPC->enemy->client->ps.velocity );
  497. }
  498. else
  499. {
  500. moveSpeed = VectorLengthSquared( NPC->enemy->s.pos.trDelta );
  501. }
  502. if ( moveSpeed )
  503. {//he's still moving, update my goalEntity's origin
  504. SandCreature_SeekEnt( NPC->enemy, 0 );
  505. NPCInfo->enemyLastSeenTime = level.time;
  506. }
  507. }
  508. }
  509. if ( (level.time-NPCInfo->enemyLastSeenTime) > 5000
  510. && !(NPC->svFlags&SVF_LOCKEDENEMY) )
  511. {//enemy hasn't moved in about 5 seconds, see if there's anything else of interest
  512. SandCreature_CheckAlerts();
  513. SandCreature_CheckMovingEnts();
  514. }
  515. float enemyDistSq = SandCreature_DistSqToGoal( qtrue );
  516. //FIXME: keeps chasing goalEntity even when it's already reached it...?
  517. if ( enemyDistSq >= MIN_ATTACK_DIST_SQ//NPCInfo->goalEntity &&
  518. && (level.time-NPCInfo->enemyLastSeenTime) <= 3000 )
  519. {//sensed enemy (or something) less than 3 seconds ago
  520. ucmd.buttons &= ~BUTTON_WALKING;
  521. if ( SandCreature_Move() )
  522. {
  523. SandCreature_MoveEffect();
  524. }
  525. }
  526. else if ( (level.time-NPCInfo->enemyLastSeenTime) <= 5000
  527. && !(NPC->svFlags&SVF_LOCKEDENEMY) )
  528. {//NOTE: this leaves a 2-second dead zone in which they'll just sit there unless their enemy moves
  529. //If there is an event we might be interested in if we weren't still interested in our enemy
  530. if ( NPC_CheckAlertEvents( qfalse, qtrue, NPCInfo->lastAlertID, qfalse, AEL_MINOR, qtrue ) >= 0 )
  531. {//just stir
  532. SandCreature_MoveEffect();
  533. }
  534. }
  535. if ( enemyDistSq < MIN_ATTACK_DIST_SQ )
  536. {
  537. if ( NPC->enemy->client )
  538. {
  539. NPC->client->ps.viewangles[YAW] = NPC->enemy->client->ps.viewangles[YAW];
  540. }
  541. if ( TIMER_Done( NPC, "breaching" ) )
  542. {
  543. //okay to attack
  544. SandCreature_Attack( qfalse );
  545. }
  546. }
  547. else if ( enemyDistSq < MAX_MISS_DIST_SQ
  548. && enemyDistSq > MIN_MISS_DIST_SQ
  549. && NPC->enemy->client
  550. && TIMER_Done( NPC, "breaching" )
  551. && TIMER_Done( NPC, "missDebounce" )
  552. && !VectorCompare( NPC->pos1, NPC->currentOrigin ) //so we don't come up again in the same spot
  553. && !Q_irand( 0, 10 ) )
  554. {
  555. if ( !(NPC->svFlags&SVF_LOCKEDENEMY) )
  556. {
  557. //miss them
  558. SandCreature_Attack( qtrue );
  559. VectorCopy( NPC->currentOrigin, NPC->pos1 );
  560. TIMER_Set( NPC, "missDebounce", Q_irand( 3000, 10000 ) );
  561. }
  562. }
  563. }
  564. void SandCreature_Hunt( void )
  565. {
  566. SandCreature_CheckAlerts();
  567. SandCreature_CheckMovingEnts();
  568. //If we have somewhere to go, then do that
  569. //FIXME: keeps chasing goalEntity even when it's already reached it...?
  570. if ( NPCInfo->goalEntity
  571. && SandCreature_DistSqToGoal( qfalse ) >= MIN_ATTACK_DIST_SQ )
  572. {
  573. ucmd.buttons |= BUTTON_WALKING;
  574. if ( SandCreature_Move() )
  575. {
  576. SandCreature_MoveEffect();
  577. }
  578. }
  579. else
  580. {
  581. NPC_ReachedGoal();
  582. }
  583. }
  584. void SandCreature_Sleep( void )
  585. {
  586. SandCreature_CheckAlerts();
  587. SandCreature_CheckMovingEnts();
  588. //FIXME: keeps chasing goalEntity even when it's already reached it!
  589. if ( NPCInfo->goalEntity
  590. && SandCreature_DistSqToGoal( qfalse ) >= MIN_ATTACK_DIST_SQ )
  591. {
  592. ucmd.buttons |= BUTTON_WALKING;
  593. if ( SandCreature_Move() )
  594. {
  595. SandCreature_MoveEffect();
  596. }
  597. }
  598. else
  599. {
  600. NPC_ReachedGoal();
  601. }
  602. /*
  603. if ( UpdateGoal() )
  604. {
  605. ucmd.buttons |= BUTTON_WALKING;
  606. //FIXME: Sand Creatures look silly using waypoints when they can go straight to the goal
  607. if ( SandCreature_Move() )
  608. {
  609. SandCreature_MoveEffect();
  610. }
  611. }
  612. */
  613. }
  614. void SandCreature_PushEnts()
  615. {
  616. int numEnts;
  617. gentity_t* radiusEnts[128];
  618. const float radius = 70;
  619. vec3_t mins, maxs;
  620. vec3_t smackDir;
  621. float smackDist;
  622. for (int i = 0; i < 3; i++ )
  623. {
  624. mins[i] = NPC->currentOrigin[i] - radius;
  625. maxs[i] = NPC->currentOrigin[i] + radius;
  626. }
  627. numEnts = gi.EntitiesInBox(mins, maxs, radiusEnts, 128);
  628. for (int entIndex=0; entIndex<numEnts; entIndex++)
  629. {
  630. // Only Clients
  631. //--------------
  632. if (!radiusEnts[entIndex] || !radiusEnts[entIndex]->client || radiusEnts[entIndex]==NPC)
  633. {
  634. continue;
  635. }
  636. // Do The Vector Distance Test
  637. //-----------------------------
  638. VectorSubtract(radiusEnts[entIndex]->currentOrigin, NPC->currentOrigin, smackDir);
  639. smackDist = VectorNormalize(smackDir);
  640. if (smackDist<radius)
  641. {
  642. G_Throw(radiusEnts[entIndex], smackDir, 90);
  643. }
  644. }
  645. }
  646. void NPC_BSSandCreature_Default( void )
  647. {
  648. qboolean visible = qfalse;
  649. //clear it every frame, will be set if we actually move this frame...
  650. NPC->s.loopSound = 0;
  651. if ( NPC->health > 0 && TIMER_Done( NPC, "breaching" ) )
  652. {//go back to non-solid mode
  653. if ( NPC->contents )
  654. {
  655. NPC->contents = 0;
  656. }
  657. if ( NPC->clipmask == MASK_NPCSOLID )
  658. {
  659. NPC->clipmask = CONTENTS_SOLID|CONTENTS_MONSTERCLIP;
  660. }
  661. if ( TIMER_Done( NPC, "speaking" ) )
  662. {
  663. G_SoundOnEnt( NPC, CHAN_VOICE, va( "sound/chars/sand_creature/voice%d.mp3", Q_irand( 1, 3 ) ) );
  664. TIMER_Set( NPC, "speaking", Q_irand( 3000, 10000 ) );
  665. }
  666. }
  667. else
  668. {//still in breaching anim
  669. visible = qtrue;
  670. //FIXME: maybe push things up/away and maybe knock people down when doing this?
  671. //FIXME: don't turn while breaching?
  672. //FIXME: move faster while breaching?
  673. //NOTE: shaking now done whenever he moves
  674. }
  675. //FIXME: when in start and end of attack/pain anims, need ground disturbance effect around him
  676. // NOTENOTE: someone stubbed this code in, so I figured I'd use it. The timers are all weird, ie, magic numbers that sort of work,
  677. // but maybe I'll try and figure out real values later if I have time.
  678. if ( NPC->client->ps.legsAnim == BOTH_ATTACK1
  679. || NPC->client->ps.legsAnim == BOTH_ATTACK2 )
  680. {//FIXME: get start and end frame numbers for this effect for each of these anims
  681. vec3_t up={0,0,1};
  682. vec3_t org;
  683. VectorCopy( NPC->currentOrigin, org );
  684. org[2] -= 40;
  685. if ( NPC->client->ps.legsAnimTimer > 3700 )
  686. {
  687. // G_PlayEffect( G_EffectIndex( "env/sand_dive" ), NPC->currentOrigin, up );
  688. G_PlayEffect( G_EffectIndex( "env/sand_spray" ), org, up );
  689. }
  690. else if ( NPC->client->ps.legsAnimTimer > 1600 && NPC->client->ps.legsAnimTimer < 1900 )
  691. {
  692. G_PlayEffect( G_EffectIndex( "env/sand_spray" ), org, up );
  693. }
  694. //G_PlayEffect( G_EffectIndex( "env/sand_attack_breach" ), org, up );
  695. }
  696. if ( !TIMER_Done( NPC, "pain" ) )
  697. {
  698. visible = qtrue;
  699. }
  700. else if ( !TIMER_Done( NPC, "attacking" ) )
  701. {
  702. visible = qtrue;
  703. }
  704. else
  705. {
  706. if ( NPC->activator )
  707. {//kill and remove the guy we ate
  708. //FIXME: want to play ...? What was I going to say?
  709. NPC->activator->health = 0;
  710. GEntity_DieFunc( NPC->activator, NPC, NPC, 1000, MOD_MELEE, 0, HL_NONE );
  711. if ( NPC->activator->s.number )
  712. {
  713. G_FreeEntity( NPC->activator );
  714. }
  715. else
  716. {//can't remove the player, just make him invisible
  717. NPC->client->ps.eFlags |= EF_NODRAW;
  718. }
  719. NPC->activator = NPC->enemy = NPCInfo->goalEntity = NULL;
  720. }
  721. if ( NPC->enemy )
  722. {
  723. SandCreature_Chase();
  724. }
  725. else if ( (level.time - NPCInfo->enemyLastSeenTime) < 5000 )//FIXME: should make this able to be variable
  726. {//we were alerted recently, move towards there and look for footsteps, etc.
  727. SandCreature_Hunt();
  728. }
  729. else
  730. {//no alerts, sleep and wake up only by alerts
  731. //FIXME: keeps chasing goalEntity even when it's already reached it!
  732. SandCreature_Sleep();
  733. }
  734. }
  735. NPC_UpdateAngles( qtrue, qtrue );
  736. if ( !visible )
  737. {
  738. NPC->client->ps.eFlags |= EF_NODRAW;
  739. NPC->s.eFlags |= EF_NODRAW;
  740. }
  741. else
  742. {
  743. NPC->client->ps.eFlags &= ~EF_NODRAW;
  744. NPC->s.eFlags &= ~EF_NODRAW;
  745. SandCreature_PushEnts();
  746. }
  747. }
  748. //FIXME: need pain behavior of sticking up through ground, writhing and screaming
  749. //FIXME: need death anim like pain, but flopping aside and staying above ground...