NPC_reactions.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164
  1. //NPC_reactions.cpp
  2. // leave this line at the top for all NPC_xxxx.cpp files...
  3. #include "g_headers.h"
  4. #include "b_local.h"
  5. #include "anims.h"
  6. #include "g_functions.h"
  7. #include "wp_saber.h"
  8. #include "g_Vehicles.h"
  9. extern qboolean G_CheckForStrongAttackMomentum( gentity_t *self );
  10. extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
  11. extern int PM_AnimLength( int index, animNumber_t anim );
  12. extern void cgi_S_StartSound( const vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx );
  13. extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
  14. extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim );
  15. extern qboolean NPC_CheckLookTarget( gentity_t *self );
  16. extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime );
  17. extern qboolean Jedi_WaitingAmbush( gentity_t *self );
  18. extern void Jedi_Ambush( gentity_t *self );
  19. extern qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker );
  20. extern qboolean PM_SaberInSpecialAttack( int anim );
  21. extern qboolean PM_SpinningSaberAnim( int anim );
  22. extern qboolean PM_SpinningAnim( int anim );
  23. extern qboolean PM_InKnockDown( playerState_t *ps );
  24. extern qboolean PM_CrouchAnim( int anim );
  25. extern qboolean PM_FlippingAnim( int anim );
  26. extern qboolean PM_RollingAnim( int anim );
  27. extern qboolean PM_InCartwheel( int anim );
  28. extern cvar_t *g_spskill;
  29. extern int teamLastEnemyTime[];
  30. extern qboolean stop_icarus;
  31. extern int killPlayerTimer;
  32. float g_crosshairEntDist = Q3_INFINITE;
  33. int g_crosshairSameEntTime = 0;
  34. int g_crosshairEntNum = ENTITYNUM_NONE;
  35. int g_crosshairEntTime = 0;
  36. /*
  37. -------------------------
  38. NPC_CheckAttacker
  39. -------------------------
  40. */
  41. static void NPC_CheckAttacker( gentity_t *other, int mod )
  42. {
  43. //FIXME: I don't see anything in here that would stop teammates from taking a teammate
  44. // as an enemy. Ideally, there would be code before this to prevent that from
  45. // happening, but that is presumptuous.
  46. //valid ent - FIXME: a VALIDENT macro would be nice here
  47. if ( !other )
  48. return;
  49. if ( other == NPC )
  50. return;
  51. if ( !other->inuse )
  52. return;
  53. //Don't take a target that doesn't want to be
  54. if ( other->flags & FL_NOTARGET )
  55. return;
  56. if ( NPC->svFlags & SVF_LOCKEDENEMY )
  57. {//IF LOCKED, CANNOT CHANGE ENEMY!!!!!
  58. return;
  59. }
  60. //If we haven't taken a target, just get mad
  61. if ( NPC->enemy == NULL )//was using "other", fixed to NPC
  62. {
  63. G_SetEnemy( NPC, other );
  64. return;
  65. }
  66. //we have an enemy, see if he's dead
  67. if ( NPC->enemy->health <= 0 )
  68. {
  69. G_ClearEnemy( NPC );
  70. G_SetEnemy( NPC, other );
  71. return;
  72. }
  73. //Don't take the same enemy again
  74. if ( other == NPC->enemy )
  75. return;
  76. if ( NPC->client->ps.weapon == WP_SABER )
  77. {//I'm a jedi
  78. if ( mod == MOD_SABER )
  79. {//I was hit by a saber FIXME: what if this was a thrown saber?
  80. //always switch to this enemy if I'm a jedi and hit by another saber
  81. G_ClearEnemy( NPC );
  82. G_SetEnemy( NPC, other );
  83. return;
  84. }
  85. }
  86. //Special case player interactions
  87. if ( other == &g_entities[0] )
  88. {
  89. //Account for the skill level to skew the results
  90. float luckThreshold;
  91. switch ( g_spskill->integer )
  92. {
  93. //Easiest difficulty, mild chance of picking up the player
  94. case 0:
  95. luckThreshold = 0.9f;
  96. break;
  97. //Medium difficulty, half-half chance of picking up the player
  98. case 1:
  99. luckThreshold = 0.5f;
  100. break;
  101. //Hardest difficulty, always turn on attacking player
  102. case 2:
  103. default:
  104. luckThreshold = 0.0f;
  105. break;
  106. }
  107. //Randomly pick up the target
  108. if ( random() > luckThreshold )
  109. {
  110. G_ClearEnemy( other );
  111. other->enemy = NPC;
  112. }
  113. return;
  114. }
  115. }
  116. void NPC_SetPainEvent( gentity_t *self )
  117. {
  118. if ( !self->NPC || !(self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
  119. {
  120. // no more borg
  121. // if( self->client->playerTeam != TEAM_BORG )
  122. // {
  123. if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
  124. {
  125. G_AddEvent( self, EV_PAIN, floor((float)self->health/self->max_health*100.0f) );
  126. }
  127. // }
  128. }
  129. }
  130. /*
  131. -------------------------
  132. NPC_GetPainChance
  133. -------------------------
  134. */
  135. float NPC_GetPainChance( gentity_t *self, int damage )
  136. {
  137. if ( !self->enemy )
  138. {//surprised, always take pain
  139. return 1.0f;
  140. }
  141. if ( damage > self->max_health/2.0f )
  142. {
  143. return 1.0f;
  144. }
  145. float pain_chance = (float)(self->max_health-self->health)/(self->max_health*2.0f) + (float)damage/(self->max_health/2.0f);
  146. switch ( g_spskill->integer )
  147. {
  148. case 0: //easy
  149. //return 0.75f;
  150. break;
  151. case 1://med
  152. pain_chance *= 0.5f;
  153. //return 0.35f;
  154. break;
  155. case 2://hard
  156. default:
  157. pain_chance *= 0.1f;
  158. //return 0.05f;
  159. break;
  160. }
  161. //Com_Printf( "%s: %4.2f\n", self->NPC_type, pain_chance );
  162. return pain_chance;
  163. }
  164. /*
  165. -------------------------
  166. NPC_ChoosePainAnimation
  167. -------------------------
  168. */
  169. #define MIN_PAIN_TIME 200
  170. extern int G_PickPainAnim( gentity_t *self, const vec3_t point, int damage, int hitLoc );
  171. void NPC_ChoosePainAnimation( gentity_t *self, gentity_t *other, const vec3_t point, int damage, int mod, int hitLoc, int voiceEvent = -1 )
  172. {
  173. //If we've already taken pain, then don't take it again
  174. if ( level.time < self->painDebounceTime && mod != MOD_ELECTROCUTE && mod != MOD_MELEE )
  175. {//FIXME: if hit while recoving from losing a saber lock, we should still play a pain anim?
  176. return;
  177. }
  178. int pain_anim = -1;
  179. float pain_chance;
  180. if ( self->s.weapon == WP_THERMAL && self->client->fireDelay > 0 )
  181. {//don't interrupt thermal throwing anim
  182. return;
  183. }
  184. else if (self->client->ps.powerups[PW_GALAK_SHIELD])
  185. {
  186. return;
  187. }
  188. else if ( self->client->NPC_class == CLASS_GALAKMECH )
  189. {
  190. if ( hitLoc == HL_GENERIC1 )
  191. {//hit the antenna!
  192. pain_chance = 1.0f;
  193. self->s.powerups |= ( 1 << PW_SHOCKED );
  194. self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 );
  195. }
  196. else if ( self->client->ps.powerups[PW_GALAK_SHIELD] )
  197. {//shield up
  198. return;
  199. }
  200. else if ( self->health > 200 && damage < 100 )
  201. {//have a *lot* of health
  202. pain_chance = 0.05f;
  203. }
  204. else
  205. {//the lower my health and greater the damage, the more likely I am to play a pain anim
  206. pain_chance = (200.0f-self->health)/100.0f + damage/50.0f;
  207. }
  208. }
  209. else if ( self->client && self->client->playerTeam == TEAM_PLAYER && other && !other->s.number )
  210. {//ally shot by player always complains
  211. pain_chance = 1.1f;
  212. }
  213. else
  214. {
  215. if ( other && other->s.weapon == WP_SABER || mod == MOD_ELECTROCUTE || mod == MOD_CRUSH/*FIXME:MOD_FORCE_GRIP*/ )
  216. {
  217. if ( self->client->ps.weapon == WP_SABER
  218. && other->s.number < MAX_CLIENTS )
  219. {//hmm, shouldn't *always* react to damage from player if I have a saber
  220. pain_chance = 1.05f - ((self->NPC->rank)/(float)RANK_CAPTAIN);
  221. }
  222. else
  223. {
  224. pain_chance = 1.0f;//always take pain from saber
  225. }
  226. }
  227. else if ( mod == MOD_GAS )
  228. {
  229. pain_chance = 1.0f;
  230. }
  231. else if ( mod == MOD_MELEE )
  232. {//higher in rank (skill) we are, less likely we are to be fazed by a punch
  233. pain_chance = 1.0f - ((RANK_CAPTAIN-self->NPC->rank)/(float)RANK_CAPTAIN);
  234. }
  235. else if ( self->client->NPC_class == CLASS_PROTOCOL )
  236. {
  237. pain_chance = 1.0f;
  238. }
  239. else
  240. {
  241. pain_chance = NPC_GetPainChance( self, damage );
  242. }
  243. if ( self->client->NPC_class == CLASS_DESANN )
  244. {
  245. pain_chance *= 0.5f;
  246. }
  247. }
  248. //See if we're going to flinch
  249. if ( random() < pain_chance )
  250. {
  251. //Pick and play our animation
  252. if ( (self->client->ps.eFlags&EF_FORCE_GRIPPED) )
  253. {
  254. G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 );
  255. }
  256. else if ( mod == MOD_GAS )
  257. {
  258. //SIGH... because our choke sounds are inappropriately long, I have to debounce them in code!
  259. if ( TIMER_Done( self, "gasChokeSound" ) )
  260. {
  261. TIMER_Set( self, "gasChokeSound", Q_irand( 1000, 2000 ) );
  262. G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 );
  263. }
  264. }
  265. else if ( (self->client->ps.eFlags&EF_FORCE_DRAINED) )
  266. {
  267. NPC_SetPainEvent( self );
  268. }
  269. else
  270. {//not being force-gripped or force-drained
  271. if ( G_CheckForStrongAttackMomentum( self )
  272. || PM_SpinningAnim( self->client->ps.legsAnim )
  273. || PM_SaberInSpecialAttack( self->client->ps.torsoAnim )
  274. || PM_InKnockDown( &self->client->ps )
  275. || PM_RollingAnim( self->client->ps.legsAnim )
  276. || (PM_FlippingAnim( self->client->ps.legsAnim )&&!PM_InCartwheel( self->client->ps.legsAnim )) )
  277. {//strong attacks, rolls, knockdowns, flips and spins cannot be interrupted by pain
  278. }
  279. else
  280. {//play an anim
  281. if ( self->client->NPC_class == CLASS_GALAKMECH )
  282. {//only has 1 for now
  283. //FIXME: never plays this, it seems...
  284. pain_anim = BOTH_PAIN1;
  285. }
  286. else if ( mod == MOD_MELEE )
  287. {
  288. pain_anim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 );
  289. }
  290. else if ( self->s.weapon == WP_SABER )
  291. {//temp HACK: these are the only 2 pain anims that look good when holding a saber
  292. pain_anim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 );
  293. }
  294. else if ( mod != MOD_ELECTROCUTE )
  295. {
  296. pain_anim = G_PickPainAnim( self, point, damage, hitLoc );
  297. }
  298. if ( pain_anim == -1 )
  299. {
  300. pain_anim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN18 );
  301. }
  302. self->client->ps.saberAnimLevel = SS_FAST;//next attack must be a quick attack
  303. self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in
  304. int parts = SETANIM_BOTH;
  305. if ( PM_CrouchAnim( self->client->ps.legsAnim ) || PM_InCartwheel( self->client->ps.legsAnim ) )
  306. {
  307. parts = SETANIM_LEGS;
  308. }
  309. self->NPC->aiFlags &= ~NPCAI_KNEEL;
  310. NPC_SetAnim( self, parts, pain_anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  311. }
  312. if ( voiceEvent != -1 )
  313. {
  314. G_AddVoiceEvent( self, voiceEvent, Q_irand( 2000, 4000 ) );
  315. }
  316. else
  317. {
  318. NPC_SetPainEvent( self );
  319. }
  320. }
  321. //Setup the timing for it
  322. if ( mod == MOD_ELECTROCUTE )
  323. {
  324. self->painDebounceTime = level.time + 4000;
  325. }
  326. self->painDebounceTime = level.time + PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t) pain_anim );
  327. self->client->fireDelay = 0;
  328. }
  329. }
  330. extern qboolean G_ValidEnemy( gentity_t *self, gentity_t *enemy );
  331. gentity_t *G_CheckControlledTurretEnemy(gentity_t *self, gentity_t *enemy, qboolean validate )
  332. {
  333. if ( enemy->e_UseFunc == useF_emplaced_gun_use
  334. || enemy->e_UseFunc == useF_eweb_use )
  335. {
  336. if ( enemy->activator
  337. && enemy->activator->client )
  338. {//return the controller of the eweb/emplaced gun
  339. if (validate==qfalse || !self->client || G_ValidEnemy(self, enemy))
  340. {
  341. return enemy->activator;
  342. }
  343. }
  344. return NULL;
  345. }
  346. return enemy;
  347. }
  348. extern void Boba_Pain( gentity_t *self, gentity_t *inflictor, int damage, int mod);
  349. /*
  350. ===============
  351. NPC_Pain
  352. ===============
  353. */
  354. void NPC_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod, int hitLoc )
  355. {
  356. team_t otherTeam = TEAM_FREE;
  357. int voiceEvent = -1;
  358. if ( self->NPC == NULL )
  359. return;
  360. if ( other == NULL )
  361. return;
  362. //or just remove ->pain in player_die?
  363. if ( self->client->ps.pm_type == PM_DEAD )
  364. return;
  365. if ( other == self )
  366. return;
  367. other = G_CheckControlledTurretEnemy(self, other, qfalse);
  368. if (!other)
  369. {
  370. return;
  371. }
  372. //MCG: Ignore damage from your own team for now
  373. if ( other->client )
  374. {
  375. otherTeam = other->client->playerTeam;
  376. // if ( otherTeam == TEAM_DISGUISE )
  377. // {
  378. // otherTeam = TEAM_PLAYER;
  379. // }
  380. }
  381. if ( self->client->playerTeam
  382. && other->client
  383. && otherTeam == self->client->playerTeam
  384. && (!player->client->ps.viewEntity || other->s.number != player->client->ps.viewEntity))
  385. {//hit by a teammate
  386. if ( other != self->enemy && self != other->enemy )
  387. {//we weren't already enemies
  388. if ( self->enemy || other->enemy
  389. || (other->s.number&&other->s.number!=player->client->ps.viewEntity)
  390. /*|| (!other->s.number&&Q_irand( 0, 3 ))*/ )
  391. {//if one of us actually has an enemy already, it's okay, just an accident OR wasn't hit by player or someone controlled by player OR player hit ally and didn't get 25% chance of getting mad (FIXME:accumulate anger+base on diff?)
  392. //FIXME: player should have to do a certain amount of damage to ally or hit them several times to make them mad
  393. //Still run pain and flee scripts
  394. if ( self->client && self->NPC )
  395. {//Run any pain instructions
  396. if ( self->health <= (self->max_health/3) && G_ActivateBehavior(self, BSET_FLEE) )
  397. {
  398. }
  399. else// if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) )
  400. {
  401. G_ActivateBehavior(self, BSET_PAIN);
  402. }
  403. }
  404. if ( damage != -1 )
  405. {//-1 == don't play pain anim
  406. //Set our proper pain animation
  407. if ( Q_irand( 0, 1 ) )
  408. {
  409. NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, EV_FFWARN );
  410. }
  411. else
  412. {
  413. NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc );
  414. }
  415. }
  416. return;
  417. }
  418. else if ( self->NPC && !other->s.number )//should be assumed, but...
  419. {//dammit, stop that!
  420. if ( self->NPC->charmedTime > level.time )
  421. {//mindtricked
  422. return;
  423. }
  424. else if ( self->NPC->ffireCount < 3+((2-g_spskill->integer)*2) )
  425. {//not mad enough yet
  426. //Com_Printf( "chck: %d < %d\n", self->NPC->ffireCount, 3+((2-g_spskill->integer)*2) );
  427. if ( damage != -1 )
  428. {//-1 == don't play pain anim
  429. //Set our proper pain animation
  430. if ( Q_irand( 0, 1 ) )
  431. {
  432. NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, EV_FFWARN );
  433. }
  434. else
  435. {
  436. NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc );
  437. }
  438. }
  439. return;
  440. }
  441. else if ( G_ActivateBehavior( self, BSET_FFIRE ) )
  442. {//we have a specific script to run, so do that instead
  443. return;
  444. }
  445. else
  446. {//okay, we're going to turn on our ally, we need to set and lock our enemy and put ourselves in a bstate that lets us attack him (and clear any flags that would stop us)
  447. self->NPC->blockedSpeechDebounceTime = 0;
  448. voiceEvent = EV_FFTURN;
  449. self->NPC->behaviorState = self->NPC->tempBehavior = self->NPC->defaultBehavior = BS_DEFAULT;
  450. other->flags &= ~FL_NOTARGET;
  451. self->svFlags &= ~(SVF_IGNORE_ENEMIES|SVF_ICARUS_FREEZE|SVF_NO_COMBAT_SOUNDS);
  452. G_SetEnemy( self, other );
  453. self->svFlags |= SVF_LOCKEDENEMY;
  454. self->NPC->scriptFlags &= ~(SCF_DONT_FIRE|SCF_CROUCHED|SCF_WALKING|SCF_NO_COMBAT_TALK|SCF_FORCED_MARCH);
  455. self->NPC->scriptFlags |= (SCF_CHASE_ENEMIES|SCF_NO_MIND_TRICK);
  456. //NOTE: we also stop ICARUS altogether
  457. stop_icarus = qtrue;
  458. if ( !killPlayerTimer )
  459. {
  460. killPlayerTimer = level.time + 10000;
  461. }
  462. }
  463. }
  464. }
  465. }
  466. SaveNPCGlobals();
  467. SetNPCGlobals( self );
  468. //Do extra bits
  469. if ( NPCInfo->ignorePain == qfalse )
  470. {
  471. NPCInfo->confusionTime = 0;//clear any charm or confusion, regardless
  472. if ( NPC->ghoul2.size() && NPC->headBolt != -1 )
  473. {
  474. G_StopEffect("force/confusion", NPC->playerModel, NPC->headBolt, NPC->s.number );
  475. }
  476. if ( damage != -1 )
  477. {//-1 == don't play pain anim
  478. //Set our proper pain animation
  479. NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, voiceEvent );
  480. }
  481. //Check to take a new enemy
  482. if ( NPC->enemy != other && NPC != other )
  483. {//not already mad at them
  484. //if it's an eweb or emplaced gun, get mad at the owner, not the gun
  485. NPC_CheckAttacker( other, mod );
  486. }
  487. }
  488. //Attempt to run any pain instructions
  489. if ( self->client && self->NPC )
  490. {
  491. //FIXME: This needs better heuristics perhaps
  492. if(self->health <= (self->max_health/3) && G_ActivateBehavior(self, BSET_FLEE) )
  493. {
  494. }
  495. else //if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) )
  496. {
  497. G_ActivateBehavior(self, BSET_PAIN);
  498. }
  499. }
  500. //Attempt to fire any paintargets we might have
  501. if( self->paintarget && self->paintarget[0] )
  502. {
  503. G_UseTargets2(self, other, self->paintarget);
  504. }
  505. if (self->client && self->client->NPC_class==CLASS_BOBAFETT)
  506. {
  507. Boba_Pain( self, inflictor, damage, mod);
  508. }
  509. RestoreNPCGlobals();
  510. }
  511. /*
  512. -------------------------
  513. NPC_Touch
  514. -------------------------
  515. */
  516. extern qboolean INV_SecurityKeyGive( gentity_t *target, const char *keyname );
  517. void NPC_Touch(gentity_t *self, gentity_t *other, trace_t *trace)
  518. {
  519. if(!self->NPC)
  520. return;
  521. SaveNPCGlobals();
  522. SetNPCGlobals( self );
  523. if ( self->message && self->health <= 0 )
  524. {//I am dead and carrying a key
  525. if ( other && player && player->health > 0 && other == player )
  526. {//player touched me
  527. char *text;
  528. qboolean keyTaken;
  529. //give him my key
  530. if ( Q_stricmp( "goodie", self->message ) == 0 )
  531. {//a goodie key
  532. if ( (keyTaken = INV_GoodieKeyGive( other )) == qtrue )
  533. {
  534. text = "cp @SP_INGAME_TOOK_IMPERIAL_GOODIE_KEY";
  535. G_AddEvent( other, EV_ITEM_PICKUP, (FindItemForInventory( INV_GOODIE_KEY )-bg_itemlist) );
  536. }
  537. else
  538. {
  539. text = "cp @SP_INGAME_CANT_CARRY_GOODIE_KEY";
  540. }
  541. }
  542. else
  543. {//a named security key
  544. if ( (keyTaken = INV_SecurityKeyGive( player, self->message )) == qtrue )
  545. {
  546. text = "cp @SP_INGAME_TOOK_IMPERIAL_SECURITY_KEY";
  547. G_AddEvent( other, EV_ITEM_PICKUP, (FindItemForInventory( INV_SECURITY_KEY )-bg_itemlist) );
  548. }
  549. else
  550. {
  551. text = "cp @SP_INGAME_CANT_CARRY_SECURITY_KEY";
  552. }
  553. }
  554. if ( keyTaken )
  555. {//remove my key
  556. gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_arm_key", 0x00000002 );
  557. self->message = NULL;
  558. self->client->ps.eFlags &= ~EF_FORCE_VISIBLE; //remove sight flag
  559. G_Sound( player, G_SoundIndex( "sound/weapons/key_pkup.wav" ) );
  560. }
  561. gi.SendServerCommand( NULL, text );
  562. }
  563. }
  564. if ( other->client )
  565. {//FIXME: if pushing against another bot, both ucmd.rightmove = 127???
  566. //Except if not facing one another...
  567. if ( other->health > 0 )
  568. {
  569. NPCInfo->touchedByPlayer = other;
  570. }
  571. if ( other == NPCInfo->goalEntity )
  572. {
  573. NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL;
  574. }
  575. if( !(self->svFlags&SVF_LOCKEDENEMY) && !(self->svFlags&SVF_IGNORE_ENEMIES) && !(other->flags & FL_NOTARGET) )
  576. {
  577. if ( self->client->enemyTeam )
  578. {//See if we bumped into an enemy
  579. if ( other->client->playerTeam == self->client->enemyTeam )
  580. {//bumped into an enemy
  581. if( NPCInfo->behaviorState != BS_HUNT_AND_KILL && !NPCInfo->tempBehavior )
  582. {//MCG - Begin: checking specific BS mode here, this is bad, a HACK
  583. //FIXME: not medics?
  584. if ( NPC->enemy != other )
  585. {//not already mad at them
  586. G_SetEnemy( NPC, other );
  587. }
  588. // NPCInfo->tempBehavior = BS_HUNT_AND_KILL;
  589. }
  590. }
  591. }
  592. }
  593. //FIXME: do this if player is moving toward me and with a certain dist?
  594. /*
  595. if ( other->s.number == 0 && self->client->playerTeam == other->client->playerTeam )
  596. {
  597. VectorAdd( self->client->pushVec, other->client->ps.velocity, self->client->pushVec );
  598. }
  599. */
  600. }
  601. else
  602. {//FIXME: check for SVF_NONNPC_ENEMY flag here?
  603. if ( other->health > 0 )
  604. {
  605. if ( NPC->enemy == other && (other->svFlags&SVF_NONNPC_ENEMY) )
  606. {
  607. NPCInfo->touchedByPlayer = other;
  608. }
  609. }
  610. if ( other == NPCInfo->goalEntity )
  611. {
  612. NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL;
  613. }
  614. }
  615. if ( NPC->client->NPC_class == CLASS_RANCOR )
  616. {//rancor
  617. if ( NPCInfo->blockedEntity != other && TIMER_Done(NPC, "blockedEntityIgnore"))
  618. {//blocked
  619. //if ( G_EntIsBreakable( other->s.number, NPC ) )
  620. {//bumped into another breakable, so take that one instead?
  621. NPCInfo->blockedEntity = other;//???
  622. }
  623. }
  624. }
  625. RestoreNPCGlobals();
  626. }
  627. /*
  628. -------------------------
  629. NPC_TempLookTarget
  630. -------------------------
  631. */
  632. void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime )
  633. {
  634. if ( !self->client )
  635. {
  636. return;
  637. }
  638. if ( !minLookTime )
  639. {
  640. minLookTime = 1000;
  641. }
  642. if ( !maxLookTime )
  643. {
  644. maxLookTime = 1000;
  645. }
  646. if ( !NPC_CheckLookTarget( self ) )
  647. {//Not already looking at something else
  648. //Look at him for 1 to 3 seconds
  649. NPC_SetLookTarget( self, lookEntNum, level.time + Q_irand( minLookTime, maxLookTime ) );
  650. }
  651. }
  652. void NPC_Respond( gentity_t *self, int userNum )
  653. {
  654. int event = -1;
  655. /*
  656. if ( Q_irand( 0, 1 ) )
  657. {
  658. event = Q_irand(EV_RESPOND1, EV_RESPOND3);
  659. }
  660. else
  661. {
  662. event = Q_irand(EV_BUSY1, EV_BUSY3);
  663. }
  664. */
  665. if ( !Q_irand( 0, 1 ) )
  666. {//set looktarget to them for a second or two
  667. NPC_TempLookTarget( self, userNum, 1000, 3000 );
  668. }
  669. //some last-minute hacked in responses
  670. switch ( self->client->NPC_class )
  671. {
  672. case CLASS_JAN:
  673. if ( self->enemy )
  674. {
  675. if ( !Q_irand( 0, 2 ) )
  676. {
  677. event = Q_irand( EV_CHASE1, EV_CHASE3 );
  678. }
  679. else if ( Q_irand( 0, 1 ) )
  680. {
  681. event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 );
  682. }
  683. else
  684. {
  685. event = Q_irand( EV_COVER1, EV_COVER5 );
  686. }
  687. }
  688. else if ( !Q_irand( 0, 2 ) )
  689. {
  690. event = EV_SUSPICIOUS4;
  691. }
  692. else if ( !Q_irand( 0, 1 ) )
  693. {
  694. event = EV_SOUND1;
  695. }
  696. else
  697. {
  698. event = EV_CONFUSE1;
  699. }
  700. break;
  701. case CLASS_LANDO:
  702. if ( self->enemy )
  703. {
  704. if ( !Q_irand( 0, 2 ) )
  705. {
  706. event = Q_irand( EV_CHASE1, EV_CHASE3 );
  707. }
  708. else if ( Q_irand( 0, 1 ) )
  709. {
  710. event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 );
  711. }
  712. else
  713. {
  714. event = Q_irand( EV_COVER1, EV_COVER5 );
  715. }
  716. }
  717. else if ( !Q_irand( 0, 6 ) )
  718. {
  719. event = EV_SIGHT2;
  720. }
  721. else if ( !Q_irand( 0, 5 ) )
  722. {
  723. event = EV_GIVEUP4;
  724. }
  725. else if ( Q_irand( 0, 4 ) > 1 )
  726. {
  727. event = Q_irand( EV_SOUND1, EV_SOUND3 );
  728. }
  729. else
  730. {
  731. event = Q_irand( EV_JDETECTED1, EV_JDETECTED2 );
  732. }
  733. break;
  734. case CLASS_LUKE:
  735. if ( self->enemy )
  736. {
  737. event = EV_COVER1;
  738. }
  739. else
  740. {
  741. event = Q_irand( EV_SOUND1, EV_SOUND3 );
  742. }
  743. break;
  744. case CLASS_JEDI:
  745. case CLASS_KYLE:
  746. if ( !self->enemy )
  747. {
  748. /*
  749. if ( !(self->svFlags&SVF_IGNORE_ENEMIES)
  750. && (self->NPC->scriptFlags&SCF_LOOK_FOR_ENEMIES)
  751. && self->client->enemyTeam == TEAM_ENEMY )
  752. {
  753. event = Q_irand( EV_ANGER1, EV_ANGER3 );
  754. }
  755. else
  756. */
  757. {
  758. event = Q_irand( EV_CONFUSE1, EV_CONFUSE3 );
  759. }
  760. }
  761. break;
  762. case CLASS_PRISONER:
  763. if ( self->enemy )
  764. {
  765. if ( Q_irand( 0, 1 ) )
  766. {
  767. event = Q_irand( EV_CHASE1, EV_CHASE3 );
  768. }
  769. else
  770. {
  771. event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 );
  772. }
  773. }
  774. else
  775. {
  776. event = Q_irand( EV_SOUND1, EV_SOUND3 );
  777. }
  778. break;
  779. case CLASS_REBEL:
  780. if ( self->enemy )
  781. {
  782. if ( !Q_irand( 0, 2 ) )
  783. {
  784. event = Q_irand( EV_CHASE1, EV_CHASE3 );
  785. }
  786. else
  787. {
  788. event = Q_irand( EV_DETECTED1, EV_DETECTED5 );
  789. }
  790. }
  791. else
  792. {
  793. event = Q_irand( EV_SOUND1, EV_SOUND3 );
  794. }
  795. break;
  796. case CLASS_BESPIN_COP:
  797. if ( !Q_stricmp( "bespincop", self->NPC_type ) )
  798. {//variant 1
  799. if ( self->enemy )
  800. {
  801. if ( Q_irand( 0, 9 ) > 6 )
  802. {
  803. event = Q_irand( EV_CHASE1, EV_CHASE3 );
  804. }
  805. else if ( Q_irand( 0, 6 ) > 4 )
  806. {
  807. event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 );
  808. }
  809. else
  810. {
  811. event = Q_irand( EV_COVER1, EV_COVER5 );
  812. }
  813. }
  814. else if ( !Q_irand( 0, 3 ) )
  815. {
  816. event = Q_irand( EV_SIGHT2, EV_SIGHT3 );
  817. }
  818. else if ( !Q_irand( 0, 1 ) )
  819. {
  820. event = Q_irand( EV_SOUND1, EV_SOUND3 );
  821. }
  822. else if ( !Q_irand( 0, 2 ) )
  823. {
  824. event = EV_LOST1;
  825. }
  826. else if ( !Q_irand( 0, 1 ) )
  827. {
  828. event = EV_ESCAPING2;
  829. }
  830. else
  831. {
  832. event = EV_GIVEUP4;
  833. }
  834. }
  835. else
  836. {//variant2
  837. if ( self->enemy )
  838. {
  839. if ( Q_irand( 0, 9 ) > 6 )
  840. {
  841. event = Q_irand( EV_CHASE1, EV_CHASE3 );
  842. }
  843. else if ( Q_irand( 0, 6 ) > 4 )
  844. {
  845. event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 );
  846. }
  847. else
  848. {
  849. event = Q_irand( EV_COVER1, EV_COVER5 );
  850. }
  851. }
  852. else if ( !Q_irand( 0, 3 ) )
  853. {
  854. event = Q_irand( EV_SIGHT1, EV_SIGHT2 );
  855. }
  856. else if ( !Q_irand( 0, 1 ) )
  857. {
  858. event = Q_irand( EV_SOUND1, EV_SOUND3 );
  859. }
  860. else if ( !Q_irand( 0, 2 ) )
  861. {
  862. event = EV_LOST1;
  863. }
  864. else if ( !Q_irand( 0, 1 ) )
  865. {
  866. event = EV_GIVEUP3;
  867. }
  868. else
  869. {
  870. event = EV_CONFUSE1;
  871. }
  872. }
  873. break;
  874. case CLASS_R2D2: // droid
  875. G_Sound(self, G_SoundIndex(va("sound/chars/r2d2/misc/r2d2talk0%d.wav",Q_irand(1, 3))));
  876. break;
  877. case CLASS_R5D2: // droid
  878. G_Sound(self, G_SoundIndex(va("sound/chars/r5d2/misc/r5talk%d.wav",Q_irand(1, 4))));
  879. break;
  880. case CLASS_MOUSE: // droid
  881. G_Sound(self, G_SoundIndex(va("sound/chars/mouse/misc/mousego%d.wav",Q_irand(1, 3))));
  882. break;
  883. case CLASS_GONK: // droid
  884. G_Sound(self, G_SoundIndex(va("sound/chars/gonk/misc/gonktalk%d.wav",Q_irand(1, 2))));
  885. break;
  886. case CLASS_JAWA:
  887. G_SoundOnEnt(self, CHAN_VOICE, va("sound/chars/jawa/misc/chatter%d.wav",Q_irand(1, 6)) );
  888. if ( self->NPC )
  889. {
  890. self->NPC->blockedSpeechDebounceTime = level.time + 2000;
  891. }
  892. break;
  893. }
  894. if ( event != -1 )
  895. {
  896. //hack here because we reuse some "combat" and "extra" sounds
  897. qboolean addFlag = (self->NPC->scriptFlags&SCF_NO_COMBAT_TALK);
  898. self->NPC->scriptFlags &= ~SCF_NO_COMBAT_TALK;
  899. G_AddVoiceEvent( self, event, 3000 );
  900. if ( addFlag )
  901. {
  902. self->NPC->scriptFlags |= SCF_NO_COMBAT_TALK;
  903. }
  904. }
  905. }
  906. /*
  907. -------------------------
  908. NPC_UseResponse
  909. -------------------------
  910. */
  911. void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone )
  912. {
  913. if ( !self->NPC || !self->client )
  914. {
  915. return;
  916. }
  917. if ( user->s.number != 0 )
  918. {//not used by the player
  919. if ( useWhenDone )
  920. {
  921. G_ActivateBehavior( self, BSET_USE );
  922. }
  923. return;
  924. }
  925. if ( user->client && self->client->playerTeam != user->client->playerTeam && self->client->playerTeam != TEAM_NEUTRAL )
  926. {//only those on the same team react
  927. if ( useWhenDone )
  928. {
  929. G_ActivateBehavior( self, BSET_USE );
  930. }
  931. return;
  932. }
  933. if ( self->NPC->blockedSpeechDebounceTime > level.time )
  934. {//I'm not responding right now
  935. return;
  936. }
  937. if ( gi.VoiceVolume[self->s.number] )
  938. {//I'm talking already
  939. if ( !useWhenDone )
  940. {//you're not trying to use me
  941. return;
  942. }
  943. }
  944. if ( useWhenDone )
  945. {
  946. G_ActivateBehavior( self, BSET_USE );
  947. }
  948. else
  949. {
  950. NPC_Respond( self, user->s.number );
  951. }
  952. }
  953. /*
  954. -------------------------
  955. NPC_Use
  956. -------------------------
  957. */
  958. extern void Add_Batteries( gentity_t *ent, int *count );
  959. void NPC_Use( gentity_t *self, gentity_t *other, gentity_t *activator )
  960. {
  961. if (self->client->ps.pm_type == PM_DEAD)
  962. {//or just remove ->pain in player_die?
  963. return;
  964. }
  965. SaveNPCGlobals();
  966. SetNPCGlobals( self );
  967. if(self->client && self->NPC)
  968. {
  969. // If this is a vehicle, let the other guy board it. Added 12/14/02 by AReis.
  970. if ( self->client->NPC_class == CLASS_VEHICLE )
  971. {
  972. Vehicle_t *pVeh = self->m_pVehicle;
  973. if ( pVeh && pVeh->m_pVehicleInfo && other && other->client )
  974. {//safety
  975. //if I used myself, eject everyone on me
  976. if ( other == self )
  977. {
  978. pVeh->m_pVehicleInfo->EjectAll( pVeh );
  979. }
  980. // If other is already riding this vehicle (self), eject him.
  981. else if ( other->owner == self )
  982. {
  983. pVeh->m_pVehicleInfo->Eject( pVeh, other, qfalse );
  984. }
  985. // Otherwise board this vehicle.
  986. else
  987. {
  988. pVeh->m_pVehicleInfo->Board( pVeh, other );
  989. }
  990. }
  991. }
  992. else if ( Jedi_WaitingAmbush( NPC ) )
  993. {
  994. Jedi_Ambush( NPC );
  995. }
  996. //Run any use instructions
  997. if ( activator && activator->s.number == 0 && self->client->NPC_class == CLASS_GONK )
  998. {
  999. // must be using the gonk, so attempt to give battery power.
  1000. // NOTE: this will steal up to MAX_BATTERIES for the activator, leaving the residual on the gonk for potential later use.
  1001. Add_Batteries( activator, &self->client->ps.batteryCharge );
  1002. }
  1003. // Not using MEDICs anymore
  1004. /*
  1005. if ( self->NPC->behaviorState == BS_MEDIC_HIDE && activator->client )
  1006. {//Heal me NOW, dammit!
  1007. if ( activator->health < activator->max_health )
  1008. {//person needs help
  1009. if ( self->NPC->eventualGoal != activator )
  1010. {//not my current patient already
  1011. NPC_TakePatient( activator );
  1012. G_ActivateBehavior( self, BSET_USE );
  1013. }
  1014. }
  1015. else if ( !self->enemy && activator->s.number == 0 && !gi.VoiceVolume[self->s.number] && !(self->NPC->scriptFlags&SCF_NO_RESPONSE) )
  1016. {//I don't have an enemy and I'm not talking and I was used by the player
  1017. NPC_UseResponse( self, other, qfalse );
  1018. }
  1019. }
  1020. */
  1021. // else if ( self->behaviorSet[BSET_USE] )
  1022. if ( self->behaviorSet[BSET_USE] )
  1023. {
  1024. NPC_UseResponse( self, other, qtrue );
  1025. }
  1026. // else if ( isMedic( self ) )
  1027. // {//Heal me NOW, dammit!
  1028. // NPC_TakePatient( activator );
  1029. // }
  1030. else if ( !self->enemy
  1031. //&& self->client->NPC_class == CLASS_VEHICLE
  1032. && activator->s.number == 0
  1033. && !gi.VoiceVolume[self->s.number]
  1034. && !(self->NPC->scriptFlags&SCF_NO_RESPONSE) )
  1035. {//I don't have an enemy and I'm not talking and I was used by the player
  1036. NPC_UseResponse( self, other, qfalse );
  1037. }
  1038. }
  1039. RestoreNPCGlobals();
  1040. }
  1041. void NPC_CheckPlayerAim( void )
  1042. {
  1043. //FIXME: need appropriate dialogue
  1044. /*
  1045. gentity_t *player = &g_entities[0];
  1046. if ( player && player->client && player->client->ps.weapon > (int)(WP_NONE) && player->client->ps.weapon < (int)(WP_TRICORDER) )
  1047. {//player has a weapon ready
  1048. if ( g_crosshairEntNum == NPC->s.number && level.time - g_crosshairEntTime < 200
  1049. && g_crosshairSameEntTime >= 3000 && g_crosshairEntDist < 256 )
  1050. {//if the player holds the crosshair on you for a few seconds
  1051. //ask them what the fuck they're doing
  1052. G_AddVoiceEvent( NPC, Q_irand( EV_FF_1A, EV_FF_1C ), 0 );
  1053. }
  1054. }
  1055. */
  1056. }
  1057. void NPC_CheckAllClear( void )
  1058. {
  1059. //FIXME: need to make this happen only once after losing enemies, not over and over again
  1060. /*
  1061. if ( NPC->client && !NPC->enemy && level.time - teamLastEnemyTime[NPC->client->playerTeam] > 10000 )
  1062. {//Team hasn't seen an enemy in 10 seconds
  1063. if ( !Q_irand( 0, 2 ) )
  1064. {
  1065. G_AddVoiceEvent( NPC, Q_irand(EV_SETTLE1, EV_SETTLE3), 3000 );
  1066. }
  1067. }
  1068. */
  1069. }