1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6
7 This file is part of the OpenJK source code.
8
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22 #include "g_headers.h"
23
24 #include "b_local.h"
25 #include "g_nav.h"
26
27 gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse );
28 extern gitem_t *FindItemForAmmo( ammo_t ammo );
29 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
30
31 //Local state enums
32 enum
33 {
34 LSTATE_NONE = 0,
35 LSTATE_BACKINGUP,
36 LSTATE_SPINNING,
37 LSTATE_PAIN,
38 LSTATE_DROP
39 };
40
41 void ImperialProbe_Idle( void );
42
NPC_Probe_Precache(void)43 void NPC_Probe_Precache(void)
44 {
45 for ( int i = 1; i < 4; i++)
46 {
47 G_SoundIndex( va( "sound/chars/probe/misc/probetalk%d", i ) );
48 }
49 G_SoundIndex( "sound/chars/probe/misc/probedroidloop" );
50 G_SoundIndex("sound/chars/probe/misc/anger1");
51 G_SoundIndex("sound/chars/probe/misc/fire");
52
53 G_EffectIndex( "probehead" );
54 G_EffectIndex( "env/med_explode2" );
55 G_EffectIndex( "probeexplosion1");
56 G_EffectIndex( "bryar/muzzle_flash" );
57
58 RegisterItem( FindItemForAmmo( AMMO_BLASTER ));
59 RegisterItem( FindItemForWeapon( WP_BRYAR_PISTOL ) );
60 }
61 /*
62 -------------------------
63 Hunter_MaintainHeight
64 -------------------------
65 */
66
67 #define VELOCITY_DECAY 0.85f
68
ImperialProbe_MaintainHeight(void)69 void ImperialProbe_MaintainHeight( void )
70 {
71 float dif;
72 // vec3_t endPos;
73 // trace_t trace;
74
75 // Update our angles regardless
76 NPC_UpdateAngles( qtrue, qtrue );
77
78 // If we have an enemy, we should try to hover at about enemy eye level
79 if ( NPC->enemy )
80 {
81 // Find the height difference
82 dif = NPC->enemy->currentOrigin[2] - NPC->currentOrigin[2];
83
84 // cap to prevent dramatic height shifts
85 if ( fabs( dif ) > 8 )
86 {
87 if ( fabs( dif ) > 16 )
88 {
89 dif = ( dif < 0 ? -16 : 16 );
90 }
91
92 NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2;
93 }
94 }
95 else
96 {
97 gentity_t *goal = NULL;
98
99 if ( NPCInfo->goalEntity ) // Is there a goal?
100 {
101 goal = NPCInfo->goalEntity;
102 }
103 else
104 {
105 goal = NPCInfo->lastGoalEntity;
106 }
107 if ( goal )
108 {
109 dif = goal->currentOrigin[2] - NPC->currentOrigin[2];
110
111 if ( fabs( dif ) > 24 )
112 {
113 ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 );
114 }
115 else
116 {
117 if ( NPC->client->ps.velocity[2] )
118 {
119 NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
120
121 if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
122 {
123 NPC->client->ps.velocity[2] = 0;
124 }
125 }
126 }
127 }
128 // Apply friction
129 else if ( NPC->client->ps.velocity[2] )
130 {
131 NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
132
133 if ( fabs( NPC->client->ps.velocity[2] ) < 1 )
134 {
135 NPC->client->ps.velocity[2] = 0;
136 }
137 }
138
139 // Stay at a given height until we take on an enemy
140 /* VectorSet( endPos, NPC->currentOrigin[0], NPC->currentOrigin[1], NPC->currentOrigin[2] - 512 );
141 gi.trace( &trace, NPC->currentOrigin, NULL, NULL, endPos, NPC->s.number, MASK_SOLID );
142
143 if ( trace.fraction != 1.0f )
144 {
145 float length = ( trace.fraction * 512 );
146
147 if ( length < 80 )
148 {
149 ucmd.upmove = 32;
150 }
151 else if ( length > 120 )
152 {
153 ucmd.upmove = -32;
154 }
155 else
156 {
157 if ( NPC->client->ps.velocity[2] )
158 {
159 NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
160
161 if ( fabs( NPC->client->ps.velocity[2] ) < 1 )
162 {
163 NPC->client->ps.velocity[2] = 0;
164 }
165 }
166 }
167 } */
168 }
169
170 // Apply friction
171 if ( NPC->client->ps.velocity[0] )
172 {
173 NPC->client->ps.velocity[0] *= VELOCITY_DECAY;
174
175 if ( fabs( NPC->client->ps.velocity[0] ) < 1 )
176 {
177 NPC->client->ps.velocity[0] = 0;
178 }
179 }
180
181 if ( NPC->client->ps.velocity[1] )
182 {
183 NPC->client->ps.velocity[1] *= VELOCITY_DECAY;
184
185 if ( fabs( NPC->client->ps.velocity[1] ) < 1 )
186 {
187 NPC->client->ps.velocity[1] = 0;
188 }
189 }
190 }
191
192 /*
193 -------------------------
194 ImperialProbe_Strafe
195 -------------------------
196 */
197
198 #define HUNTER_STRAFE_VEL 256
199 #define HUNTER_STRAFE_DIS 200
200 #define HUNTER_UPWARD_PUSH 32
201
ImperialProbe_Strafe(void)202 void ImperialProbe_Strafe( void )
203 {
204 int dir;
205 vec3_t end, right;
206 trace_t tr;
207
208 AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL );
209
210 // Pick a random strafe direction, then check to see if doing a strafe would be
211 // reasonable valid
212 dir = ( rand() & 1 ) ? -1 : 1;
213 VectorMA( NPC->currentOrigin, HUNTER_STRAFE_DIS * dir, right, end );
214
215 gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
216
217 // Close enough
218 if ( tr.fraction > 0.9f )
219 {
220 VectorMA( NPC->client->ps.velocity, HUNTER_STRAFE_VEL * dir, right, NPC->client->ps.velocity );
221
222 // Add a slight upward push
223 NPC->client->ps.velocity[2] += HUNTER_UPWARD_PUSH;
224
225 // Set the strafe start time so we can do a controlled roll
226 NPC->fx_time = level.time;
227 NPCInfo->standTime = level.time + 3000 + Q_flrand(0.0f, 1.0f) * 500;
228 }
229 }
230
231 /*
232 -------------------------
233 ImperialProbe_Hunt
234 -------------------------`
235 */
236
237 #define HUNTER_FORWARD_BASE_SPEED 10
238 #define HUNTER_FORWARD_MULTIPLIER 5
239
ImperialProbe_Hunt(qboolean visible,qboolean advance)240 void ImperialProbe_Hunt( qboolean visible, qboolean advance )
241 {
242 float distance, speed;
243 vec3_t forward;
244
245 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
246
247 //If we're not supposed to stand still, pursue the player
248 if ( NPCInfo->standTime < level.time )
249 {
250 // Only strafe when we can see the player
251 if ( visible )
252 {
253 ImperialProbe_Strafe();
254 return;
255 }
256 }
257
258 //If we don't want to advance, stop here
259 if ( advance == qfalse )
260 return;
261
262 //Only try and navigate if the player is visible
263 if ( visible == qfalse )
264 {
265 // Move towards our goal
266 NPCInfo->goalEntity = NPC->enemy;
267 NPCInfo->goalRadius = 12;
268
269 //Get our direction from the navigator if we can't see our target
270 if ( NPC_GetMoveDirection( forward, &distance ) == qfalse )
271 return;
272 }
273 else
274 {
275 VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward );
276 /*distance = */VectorNormalize( forward );
277 }
278
279 speed = HUNTER_FORWARD_BASE_SPEED + HUNTER_FORWARD_MULTIPLIER * g_spskill->integer;
280 VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity );
281 }
282
283 /*
284 -------------------------
285 ImperialProbe_FireBlaster
286 -------------------------
287 */
ImperialProbe_FireBlaster(void)288 void ImperialProbe_FireBlaster(void)
289 {
290 vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1;
291 static vec3_t forward, vright, up;
292 gentity_t *missile;
293 mdxaBone_t boltMatrix;
294
295 //FIXME: use {0, NPC->client->ps.legsYaw, 0}
296 gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel,
297 NPC->genericBolt1,
298 &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time),
299 NULL, NPC->s.modelScale );
300
301 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle1 );
302
303 G_PlayEffect( "bryar/muzzle_flash", muzzle1 );
304
305 G_Sound( NPC, G_SoundIndex( "sound/chars/probe/misc/fire" ));
306
307 if (NPC->health)
308 {
309 CalcEntitySpot( NPC->enemy, SPOT_CHEST, enemy_org1 );
310 enemy_org1[0]+= Q_irand(0,10);
311 enemy_org1[1]+= Q_irand(0,10);
312 VectorSubtract (enemy_org1, muzzle1, delta1);
313 vectoangles ( delta1, angleToEnemy1 );
314 AngleVectors (angleToEnemy1, forward, vright, up);
315 }
316 else
317 {
318 AngleVectors (NPC->currentAngles, forward, vright, up);
319 }
320
321 missile = CreateMissile( muzzle1, forward, 1600, 10000, NPC );
322
323 missile->classname = "bryar_proj";
324 missile->s.weapon = WP_BRYAR_PISTOL;
325
326 if ( g_spskill->integer <= 1 )
327 {
328 missile->damage = 5;
329 }
330 else
331 {
332 missile->damage = 10;
333 }
334
335
336 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
337 missile->methodOfDeath = MOD_ENERGY;
338 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
339
340 }
341
342 /*
343 -------------------------
344 ImperialProbe_Ranged
345 -------------------------
346 */
ImperialProbe_Ranged(qboolean visible,qboolean advance)347 void ImperialProbe_Ranged( qboolean visible, qboolean advance )
348 {
349 int delay_min,delay_max;
350
351 if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack?
352 {
353
354 if ( g_spskill->integer == 0 )
355 {
356 delay_min = 500;
357 delay_max = 3000;
358 }
359 else if ( g_spskill->integer > 1 )
360 {
361 delay_min = 500;
362 delay_max = 2000;
363 }
364 else
365 {
366 delay_min = 300;
367 delay_max = 1500;
368 }
369
370 TIMER_Set( NPC, "attackDelay", Q_irand( delay_min, delay_max ) );
371 ImperialProbe_FireBlaster();
372 // ucmd.buttons |= BUTTON_ATTACK;
373 }
374
375 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
376 {
377 ImperialProbe_Hunt( visible, advance );
378 }
379 }
380
381 /*
382 -------------------------
383 ImperialProbe_AttackDecision
384 -------------------------
385 */
386
387 #define MIN_MELEE_RANGE 320
388 #define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE )
389
390 #define MIN_DISTANCE 128
391 #define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE )
392
ImperialProbe_AttackDecision(void)393 void ImperialProbe_AttackDecision( void )
394 {
395 // Always keep a good height off the ground
396 ImperialProbe_MaintainHeight();
397
398 //randomly talk
399 if ( TIMER_Done(NPC,"patrolNoise") )
400 {
401 if (TIMER_Done(NPC,"angerNoise"))
402 {
403 G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d", Q_irand(1, 3)) );
404
405 TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) );
406 }
407 }
408
409 // If we don't have an enemy, just idle
410 if ( NPC_CheckEnemyExt() == qfalse )
411 {
412 ImperialProbe_Idle();
413 return;
414 }
415
416 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_NORMAL);
417
418 // Rate our distance to the target, and our visibilty
419 float distance = (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
420 // distance_e distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE;
421 qboolean visible = NPC_ClearLOS( NPC->enemy );
422 qboolean advance = (qboolean)(distance > MIN_DISTANCE_SQR);
423
424 // If we cannot see our target, move to see it
425 if ( visible == qfalse )
426 {
427 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
428 {
429 ImperialProbe_Hunt( visible, advance );
430 return;
431 }
432 }
433
434 // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb
435 NPC_FaceEnemy( qtrue );
436
437 // Decide what type of attack to do
438 ImperialProbe_Ranged( visible, advance );
439 }
440
441 /*
442 -------------------------
443 NPC_BSDroid_Pain
444 -------------------------
445 */
NPC_Probe_Pain(gentity_t * self,gentity_t * inflictor,gentity_t * other,vec3_t point,int damage,int mod,int hitLoc)446 void NPC_Probe_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod,int hitLoc )
447 {
448 float pain_chance;
449
450 VectorCopy( self->NPC->lastPathAngles, self->s.angles );
451
452 if ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) // demp2 always messes them up real good
453 {
454 vec3_t endPos;
455 trace_t trace;
456
457 VectorSet( endPos, self->currentOrigin[0], self->currentOrigin[1], self->currentOrigin[2] - 128 );
458 gi.trace( &trace, self->currentOrigin, NULL, NULL, endPos, self->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
459
460 if ( trace.fraction == 1.0f || mod == MOD_DEMP2 ) // demp2 always does this
461 {
462 if (self->client->clientInfo.headModel != 0)
463 {
464 vec3_t origin;
465
466 VectorCopy(self->currentOrigin,origin);
467 origin[2] +=50;
468 // G_PlayEffect( "small_chunks", origin );
469 G_PlayEffect( "probehead", origin );
470 G_PlayEffect( "env/med_explode2", origin );
471 self->client->clientInfo.headModel = 0;
472 self->NPC->stats.moveType = MT_RUNJUMP;
473 self->client->ps.gravity = g_gravity->value*.1;
474 }
475
476 if ( (mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT) && other )
477 {
478 vec3_t dir;
479
480 NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
481
482 VectorSubtract( self->currentOrigin, other->currentOrigin, dir );
483 VectorNormalize( dir );
484
485 VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity );
486 self->client->ps.velocity[2] -= 127;
487 }
488
489 self->s.powerups |= ( 1 << PW_SHOCKED );
490 self->client->ps.powerups[PW_SHOCKED] = level.time + 3000;
491
492 self->NPC->localState = LSTATE_DROP;
493 }
494 }
495 else
496 {
497 pain_chance = NPC_GetPainChance( self, damage );
498
499 if ( Q_flrand(0.0f, 1.0f) < pain_chance ) // Spin around in pain?
500 {
501 NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE);
502 }
503 }
504
505 NPC_Pain( self, inflictor, other, point, damage, mod);
506 }
507
508 /*
509 -------------------------
510 ImperialProbe_Idle
511 -------------------------
512 */
513
ImperialProbe_Idle(void)514 void ImperialProbe_Idle( void )
515 {
516 ImperialProbe_MaintainHeight();
517
518 NPC_BSIdle();
519 }
520
521 /*
522 -------------------------
523 NPC_BSImperialProbe_Patrol
524 -------------------------
525 */
ImperialProbe_Patrol(void)526 void ImperialProbe_Patrol( void )
527 {
528 ImperialProbe_MaintainHeight();
529
530 if ( NPC_CheckPlayerTeamStealth() )
531 {
532 NPC_UpdateAngles( qtrue, qtrue );
533 return;
534 }
535
536 //If we have somewhere to go, then do that
537 if (!NPC->enemy)
538 {
539 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_NORMAL );
540
541 if ( UpdateGoal() )
542 {
543 //start loop sound once we move
544 NPC->s.loopSound = G_SoundIndex( "sound/chars/probe/misc/probedroidloop" );
545 ucmd.buttons |= BUTTON_WALKING;
546 NPC_MoveToGoal( qtrue );
547 }
548 //randomly talk
549 if (TIMER_Done(NPC,"patrolNoise"))
550 {
551 G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d", Q_irand(1, 3)) );
552
553 TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
554 }
555 }
556 else // He's got an enemy. Make him angry.
557 {
558 G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/probe/misc/anger1" );
559 TIMER_Set( NPC, "angerNoise", Q_irand( 2000, 4000 ) );
560 //NPCInfo->behaviorState = BS_HUNT_AND_KILL;
561 }
562
563 NPC_UpdateAngles( qtrue, qtrue );
564 }
565
566 /*
567 -------------------------
568 ImperialProbe_Wait
569 -------------------------
570 */
ImperialProbe_Wait(void)571 void ImperialProbe_Wait(void)
572 {
573 if ( NPCInfo->localState == LSTATE_DROP )
574 {
575 vec3_t endPos;
576 trace_t trace;
577
578 NPCInfo->desiredYaw = AngleNormalize360( NPCInfo->desiredYaw + 25 );
579
580 VectorSet( endPos, NPC->currentOrigin[0], NPC->currentOrigin[1], NPC->currentOrigin[2] - 32 );
581 gi.trace( &trace, NPC->currentOrigin, NULL, NULL, endPos, NPC->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
582
583 if ( trace.fraction != 1.0f )
584 {
585 G_Damage(NPC, NPC->enemy, NPC->enemy, NULL, NULL, 2000, 0,MOD_UNKNOWN);
586 }
587 }
588
589 NPC_UpdateAngles( qtrue, qtrue );
590 }
591
592 /*
593 -------------------------
594 NPC_BSImperialProbe_Default
595 -------------------------
596 */
NPC_BSImperialProbe_Default(void)597 void NPC_BSImperialProbe_Default( void )
598 {
599
600 if ( NPC->enemy )
601 {
602 NPCInfo->goalEntity = NPC->enemy;
603 ImperialProbe_AttackDecision();
604 }
605 else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
606 {
607 ImperialProbe_Patrol();
608 }
609 else if ( NPCInfo->localState == LSTATE_DROP )
610 {
611 ImperialProbe_Wait();
612 }
613 else
614 {
615 ImperialProbe_Idle();
616 }
617 }
618