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 void Remote_Strafe( void );
29 
30 #define VELOCITY_DECAY	0.85f
31 
32 
33 //Local state enums
34 enum
35 {
36 	LSTATE_NONE = 0,
37 };
38 
39 void Remote_Idle( void );
40 
NPC_Remote_Precache(void)41 void NPC_Remote_Precache(void)
42 {
43 	G_SoundIndex("sound/chars/remote/misc/fire.wav");
44 	G_SoundIndex( "sound/chars/remote/misc/hiss.wav");
45 	G_EffectIndex( "env/small_explode");
46 }
47 
48 /*
49 -------------------------
50 NPC_Remote_Pain
51 -------------------------
52 */
NPC_Remote_Pain(gentity_t * self,gentity_t * inflictor,gentity_t * other,vec3_t point,int damage,int mod,int hitLoc)53 void NPC_Remote_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod,int hitLoc )
54 {
55 	SaveNPCGlobals();
56 	SetNPCGlobals( self );
57 	Remote_Strafe();
58 	RestoreNPCGlobals();
59 
60 	NPC_Pain( self, inflictor, other, point, damage, mod );
61 }
62 
63 /*
64 -------------------------
65 Remote_MaintainHeight
66 -------------------------
67 */
Remote_MaintainHeight(void)68 void Remote_MaintainHeight( void )
69 {
70 	float	dif;
71 
72 	// Update our angles regardless
73 	NPC_UpdateAngles( qtrue, qtrue );
74 
75 	if ( NPC->client->ps.velocity[2] )
76 	{
77 		NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
78 
79 		if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
80 		{
81 			NPC->client->ps.velocity[2] = 0;
82 		}
83 	}
84 	// If we have an enemy, we should try to hover at or a little below enemy eye level
85 	if ( NPC->enemy )
86 	{
87 		if (TIMER_Done( NPC, "heightChange"))
88 		{
89 			TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 ));
90 
91 			// Find the height difference
92 			dif = (NPC->enemy->currentOrigin[2] +  Q_irand( 0, NPC->enemy->maxs[2]+8 )) - NPC->currentOrigin[2];
93 
94 			// cap to prevent dramatic height shifts
95 			if ( fabs( dif ) > 2 )
96 			{
97 				if ( fabs( dif ) > 24 )
98 				{
99 					dif = ( dif < 0 ? -24 : 24 );
100 				}
101 				dif *= 10;
102 				NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2;
103 				NPC->fx_time = level.time;
104 				G_Sound( NPC, G_SoundIndex("sound/chars/remote/misc/hiss.wav"));
105 			}
106 		}
107 	}
108 	else
109 	{
110 		gentity_t *goal = NULL;
111 
112 		if ( NPCInfo->goalEntity )	// Is there a goal?
113 		{
114 			goal = NPCInfo->goalEntity;
115 		}
116 		else
117 		{
118 			goal = NPCInfo->lastGoalEntity;
119 		}
120 		if ( goal )
121 		{
122 			dif = goal->currentOrigin[2] - NPC->currentOrigin[2];
123 
124 			if ( fabs( dif ) > 24 )
125 			{
126 				dif = ( dif < 0 ? -24 : 24 );
127 				NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2;
128 			}
129 		}
130 	}
131 
132 	// Apply friction
133 	if ( NPC->client->ps.velocity[0] )
134 	{
135 		NPC->client->ps.velocity[0] *= VELOCITY_DECAY;
136 
137 		if ( fabs( NPC->client->ps.velocity[0] ) < 1 )
138 		{
139 			NPC->client->ps.velocity[0] = 0;
140 		}
141 	}
142 
143 	if ( NPC->client->ps.velocity[1] )
144 	{
145 		NPC->client->ps.velocity[1] *= VELOCITY_DECAY;
146 
147 		if ( fabs( NPC->client->ps.velocity[1] ) < 1 )
148 		{
149 			NPC->client->ps.velocity[1] = 0;
150 		}
151 	}
152 }
153 
154 #define REMOTE_STRAFE_VEL	256
155 #define REMOTE_STRAFE_DIS	200
156 #define REMOTE_UPWARD_PUSH	32
157 
158 /*
159 -------------------------
160 Remote_Strafe
161 -------------------------
162 */
Remote_Strafe(void)163 void Remote_Strafe( void )
164 {
165 	int		dir;
166 	vec3_t	end, right;
167 	trace_t	tr;
168 
169 	AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL );
170 
171 	// Pick a random strafe direction, then check to see if doing a strafe would be
172 	//	reasonable valid
173 	dir = ( rand() & 1 ) ? -1 : 1;
174 	VectorMA( NPC->currentOrigin, REMOTE_STRAFE_DIS * dir, right, end );
175 
176 	gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
177 
178 	// Close enough
179 	if ( tr.fraction > 0.9f )
180 	{
181 		VectorMA( NPC->client->ps.velocity, REMOTE_STRAFE_VEL * dir, right, NPC->client->ps.velocity );
182 
183 		G_Sound( NPC, G_SoundIndex("sound/chars/remote/misc/hiss.wav"));
184 
185 		// Add a slight upward push
186 		NPC->client->ps.velocity[2] += REMOTE_UPWARD_PUSH;
187 
188 		// Set the strafe start time so we can do a controlled roll
189 		NPC->fx_time = level.time;
190 		NPCInfo->standTime = level.time + 3000 + Q_flrand(0.0f, 1.0f) * 500;
191 	}
192 }
193 
194 #define REMOTE_FORWARD_BASE_SPEED	10
195 #define REMOTE_FORWARD_MULTIPLIER	5
196 
197 /*
198 -------------------------
199 Remote_Hunt
200 -------------------------
201 */
Remote_Hunt(qboolean visible,qboolean advance,qboolean retreat)202 void Remote_Hunt( qboolean visible, qboolean advance, qboolean retreat )
203 {
204 	float	distance, speed;
205 	vec3_t	forward;
206 
207 	//If we're not supposed to stand still, pursue the player
208 	if ( NPCInfo->standTime < level.time )
209 	{
210 		// Only strafe when we can see the player
211 		if ( visible )
212 		{
213 			Remote_Strafe();
214 			return;
215 		}
216 	}
217 
218 	//If we don't want to advance, stop here
219 	if ( advance == qfalse && visible == qtrue )
220 		return;
221 
222 	//Only try and navigate if the player is visible
223 	if ( visible == qfalse )
224 	{
225 		// Move towards our goal
226 		NPCInfo->goalEntity = NPC->enemy;
227 		NPCInfo->goalRadius = 12;
228 
229 		//Get our direction from the navigator if we can't see our target
230 		if ( NPC_GetMoveDirection( forward, &distance ) == qfalse )
231 			return;
232 	}
233 	else
234 	{
235 		VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward );
236 		/*distance = */VectorNormalize( forward );
237 	}
238 
239 	speed = REMOTE_FORWARD_BASE_SPEED + REMOTE_FORWARD_MULTIPLIER * g_spskill->integer;
240 	if ( retreat == qtrue )
241 	{
242 		speed *= -1;
243 	}
244 	VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity );
245 }
246 
247 
248 /*
249 -------------------------
250 Remote_Fire
251 -------------------------
252 */
Remote_Fire(void)253 void Remote_Fire (void)
254 {
255 	vec3_t	delta1, enemy_org1, muzzle1;
256 	vec3_t	angleToEnemy1;
257 	static	vec3_t	forward, vright, up;
258 	gentity_t	*missile;
259 
260 	CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org1 );
261 	VectorCopy( NPC->currentOrigin, muzzle1 );
262 
263 	VectorSubtract (enemy_org1, muzzle1, delta1);
264 
265 	vectoangles ( delta1, angleToEnemy1 );
266 	AngleVectors (angleToEnemy1, forward, vright, up);
267 
268 	missile = CreateMissile( NPC->currentOrigin, forward, 1000, 10000, NPC );
269 
270 	G_PlayEffect( "bryar/muzzle_flash", NPC->currentOrigin, forward );
271 
272 	missile->classname = "briar";
273 	missile->s.weapon = WP_BRYAR_PISTOL;
274 
275 	missile->damage = 10;
276 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
277 	missile->methodOfDeath = MOD_ENERGY;
278 	missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
279 
280 }
281 
282 /*
283 -------------------------
284 Remote_Ranged
285 -------------------------
286 */
Remote_Ranged(qboolean visible,qboolean advance,qboolean retreat)287 void Remote_Ranged( qboolean visible, qboolean advance, qboolean retreat )
288 {
289 
290 	if ( TIMER_Done( NPC, "attackDelay" ) )	// Attack?
291 	{
292 		TIMER_Set( NPC, "attackDelay", Q_irand( 500, 3000 ) );
293 		Remote_Fire();
294 	}
295 
296 	if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
297 	{
298 		Remote_Hunt( visible, advance, retreat );
299 	}
300 }
301 
302 #define	MIN_MELEE_RANGE		320
303 #define	MIN_MELEE_RANGE_SQR	( MIN_MELEE_RANGE * MIN_MELEE_RANGE )
304 
305 #define MIN_DISTANCE		80
306 #define MIN_DISTANCE_SQR	( MIN_DISTANCE * MIN_DISTANCE )
307 
308 /*
309 -------------------------
310 Remote_Attack
311 -------------------------
312 */
Remote_Attack(void)313 void Remote_Attack( void )
314 {
315 	if ( TIMER_Done(NPC,"spin") )
316 	{
317 		TIMER_Set( NPC, "spin", Q_irand( 250, 1500 ) );
318 		NPCInfo->desiredYaw += Q_irand( -200, 200 );
319 	}
320 	// Always keep a good height off the ground
321 	Remote_MaintainHeight();
322 
323 	// If we don't have an enemy, just idle
324 	if ( NPC_CheckEnemyExt() == qfalse )
325 	{
326 		Remote_Idle();
327 		return;
328 	}
329 
330 	// Rate our distance to the target, and our visibilty
331 	float		distance	= (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
332 //	distance_e	distRate	= ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE;
333 	qboolean	visible		= NPC_ClearLOS( NPC->enemy );
334 	float		idealDist	= MIN_DISTANCE_SQR+(MIN_DISTANCE_SQR*Q_flrand( 0, 1 ));
335 	qboolean	advance		= (qboolean)(distance > idealDist*1.25);
336 	qboolean	retreat		= (qboolean)(distance < idealDist*0.75);
337 
338 	// If we cannot see our target, move to see it
339 	if ( visible == qfalse )
340 	{
341 		if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
342 		{
343 			Remote_Hunt( visible, advance, retreat );
344 			return;
345 		}
346 	}
347 
348 	Remote_Ranged( visible, advance, retreat );
349 
350 }
351 
352 /*
353 -------------------------
354 Remote_Idle
355 -------------------------
356 */
Remote_Idle(void)357 void Remote_Idle( void )
358 {
359 	Remote_MaintainHeight();
360 
361 	NPC_BSIdle();
362 }
363 
364 /*
365 -------------------------
366 Remote_Patrol
367 -------------------------
368 */
Remote_Patrol(void)369 void Remote_Patrol( void )
370 {
371 	Remote_MaintainHeight();
372 
373 	//If we have somewhere to go, then do that
374 	if (!NPC->enemy)
375 	{
376 		if ( UpdateGoal() )
377 		{
378 			//start loop sound once we move
379 			ucmd.buttons |= BUTTON_WALKING;
380 			NPC_MoveToGoal( qtrue );
381 		}
382 	}
383 
384 	NPC_UpdateAngles( qtrue, qtrue );
385 }
386 
387 
388 /*
389 -------------------------
390 NPC_BSRemote_Default
391 -------------------------
392 */
NPC_BSRemote_Default(void)393 void NPC_BSRemote_Default( void )
394 {
395 	if ( NPC->enemy )
396 	{
397 		Remote_Attack();
398 	}
399 	else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
400 	{
401 		Remote_Patrol();
402 	}
403 	else
404 	{
405 		Remote_Idle();
406 	}
407 }