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