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 }