1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7
8 This file is part of the OpenJK source code.
9
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23
24 // g_weapon.c
25 // perform the server side effects of a weapon firing
26
27 #include "g_local.h"
28 #include "botlib/be_aas.h"
29 #include "bg_saga.h"
30 #include "ghoul2/G2.h"
31 #include "qcommon/q_shared.h"
32
33 static vec3_t forward, vright, up;
34 static vec3_t muzzle;
35
36 // Bryar Pistol
37 //--------
38 #define BRYAR_PISTOL_VEL 1600
39 #define BRYAR_PISTOL_DAMAGE 10
40 #define BRYAR_CHARGE_UNIT 200.0f // bryar charging gives us one more unit every 200ms--if you change this, you'll have to do the same in bg_pmove
41 #define BRYAR_ALT_SIZE 1.0f
42
43 // E11 Blaster
44 //---------
45 #define BLASTER_SPREAD 1.6f//1.2f
46 #define BLASTER_VELOCITY 2300
47 #define BLASTER_DAMAGE 20
48
49 // Tenloss Disruptor
50 //----------
51 #define DISRUPTOR_MAIN_DAMAGE 30 //40
52 #define DISRUPTOR_MAIN_DAMAGE_SIEGE 50
53 #define DISRUPTOR_NPC_MAIN_DAMAGE_CUT 0.25f
54
55 #define DISRUPTOR_ALT_DAMAGE 100 //125
56 #define DISRUPTOR_NPC_ALT_DAMAGE_CUT 0.2f
57 #define DISRUPTOR_ALT_TRACES 3 // can go through a max of 3 damageable(sp?) entities
58 #define DISRUPTOR_CHARGE_UNIT 50.0f // distruptor charging gives us one more unit every 50ms--if you change this, you'll have to do the same in bg_pmove
59
60 // Wookiee Bowcaster
61 //----------
62 #define BOWCASTER_DAMAGE 50
63 #define BOWCASTER_VELOCITY 1300
64 #define BOWCASTER_SPLASH_DAMAGE 0
65 #define BOWCASTER_SPLASH_RADIUS 0
66 #define BOWCASTER_SIZE 2
67
68 #define BOWCASTER_ALT_SPREAD 5.0f
69 #define BOWCASTER_VEL_RANGE 0.3f
70 #define BOWCASTER_CHARGE_UNIT 200.0f // bowcaster charging gives us one more unit every 200ms--if you change this, you'll have to do the same in bg_pmove
71
72 // Heavy Repeater
73 //----------
74 #define REPEATER_SPREAD 1.4f
75 #define REPEATER_DAMAGE 14
76 #define REPEATER_VELOCITY 1600
77
78 #define REPEATER_ALT_SIZE 3 // half of bbox size
79 #define REPEATER_ALT_DAMAGE 60
80 #define REPEATER_ALT_SPLASH_DAMAGE 60
81 #define REPEATER_ALT_SPLASH_RADIUS 128
82 #define REPEATER_ALT_SPLASH_RAD_SIEGE 80
83 #define REPEATER_ALT_VELOCITY 1100
84
85 // DEMP2
86 //----------
87 #define DEMP2_DAMAGE 35
88 #define DEMP2_VELOCITY 1800
89 #define DEMP2_SIZE 2 // half of bbox size
90
91 #define DEMP2_ALT_DAMAGE 8 //12 // does 12, 36, 84 at each of the 3 charge levels.
92 #define DEMP2_CHARGE_UNIT 700.0f // demp2 charging gives us one more unit every 700ms--if you change this, you'll have to do the same in bg_weapons
93 #define DEMP2_ALT_RANGE 4096
94 #define DEMP2_ALT_SPLASHRADIUS 256
95
96 // Golan Arms Flechette
97 //---------
98 #define FLECHETTE_SHOTS 5
99 #define FLECHETTE_SPREAD 4.0f
100 #define FLECHETTE_DAMAGE 12//15
101 #define FLECHETTE_VEL 3500
102 #define FLECHETTE_SIZE 1
103 #define FLECHETTE_MINE_RADIUS_CHECK 256
104 #define FLECHETTE_ALT_DAMAGE 60
105 #define FLECHETTE_ALT_SPLASH_DAM 60
106 #define FLECHETTE_ALT_SPLASH_RAD 128
107
108 // Personal Rocket Launcher
109 //---------
110 #define ROCKET_VELOCITY 900
111 #define ROCKET_DAMAGE 100
112 #define ROCKET_SPLASH_DAMAGE 100
113 #define ROCKET_SPLASH_RADIUS 160
114 #define ROCKET_SIZE 3
115 #define ROCKET_ALT_THINK_TIME 100
116
117 // Concussion Rifle
118 //---------
119 //primary
120 //man, this thing is too absurdly powerful. having to
121 //slash the values way down from sp.
122 #define CONC_VELOCITY 3000
123 #define CONC_DAMAGE 75 //150
124 #define CONC_NPC_DAMAGE_EASY 40
125 #define CONC_NPC_DAMAGE_NORMAL 80
126 #define CONC_NPC_DAMAGE_HARD 100
127 #define CONC_SPLASH_DAMAGE 40 //50
128 #define CONC_SPLASH_RADIUS 200 //300
129 //alt
130 #define CONC_ALT_DAMAGE 25 //100
131 #define CONC_ALT_NPC_DAMAGE_EASY 20
132 #define CONC_ALT_NPC_DAMAGE_MEDIUM 35
133 #define CONC_ALT_NPC_DAMAGE_HARD 50
134
135 // Stun Baton
136 //--------------
137 #define STUN_BATON_DAMAGE 20
138 #define STUN_BATON_ALT_DAMAGE 20
139 #define STUN_BATON_RANGE 8
140
141 // Melee
142 //--------------
143 #define MELEE_SWING1_DAMAGE 10
144 #define MELEE_SWING2_DAMAGE 12
145 #define MELEE_RANGE 8
146
147 // ATST Main Gun
148 //--------------
149 #define ATST_MAIN_VEL 4000 //
150 #define ATST_MAIN_DAMAGE 25 //
151 #define ATST_MAIN_SIZE 3 // make it easier to hit things
152
153 // ATST Side Gun
154 //---------------
155 #define ATST_SIDE_MAIN_DAMAGE 75
156 #define ATST_SIDE_MAIN_VELOCITY 1300
157 #define ATST_SIDE_MAIN_NPC_DAMAGE_EASY 30
158 #define ATST_SIDE_MAIN_NPC_DAMAGE_NORMAL 40
159 #define ATST_SIDE_MAIN_NPC_DAMAGE_HARD 50
160 #define ATST_SIDE_MAIN_SIZE 4
161 #define ATST_SIDE_MAIN_SPLASH_DAMAGE 10 // yeah, pretty small, either zero out or make it worth having?
162 #define ATST_SIDE_MAIN_SPLASH_RADIUS 16 // yeah, pretty small, either zero out or make it worth having?
163
164 #define ATST_SIDE_ALT_VELOCITY 1100
165 #define ATST_SIDE_ALT_NPC_VELOCITY 600
166 #define ATST_SIDE_ALT_DAMAGE 130
167
168 #define ATST_SIDE_ROCKET_NPC_DAMAGE_EASY 30
169 #define ATST_SIDE_ROCKET_NPC_DAMAGE_NORMAL 50
170 #define ATST_SIDE_ROCKET_NPC_DAMAGE_HARD 90
171
172 #define ATST_SIDE_ALT_SPLASH_DAMAGE 130
173 #define ATST_SIDE_ALT_SPLASH_RADIUS 200
174 #define ATST_SIDE_ALT_ROCKET_SIZE 5
175 #define ATST_SIDE_ALT_ROCKET_SPLASH_SCALE 0.5f // scales splash for NPC's
176
177 extern qboolean G_BoxInBounds( vec3_t point, vec3_t mins, vec3_t maxs, vec3_t boundsMins, vec3_t boundsMaxs );
178 extern qboolean G_HeavyMelee( gentity_t *attacker );
179 extern void Jedi_Decloak( gentity_t *self );
180
181 static void WP_FireEmplaced( gentity_t *ent, qboolean altFire );
182
183 void laserTrapStick( gentity_t *ent, vec3_t endpos, vec3_t normal );
184
touch_NULL(gentity_t * ent,gentity_t * other,trace_t * trace)185 void touch_NULL( gentity_t *ent, gentity_t *other, trace_t *trace )
186 {
187
188 }
189
190 void laserTrapExplode( gentity_t *self );
191 void RocketDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod);
192
193 //We should really organize weapon data into tables or parse from the ext data so we have accurate info for this,
WP_SpeedOfMissileForWeapon(int wp,qboolean alt_fire)194 float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire )
195 {
196 return 500;
197 }
198
199 //-----------------------------------------------------------------------------
W_TraceSetStart(gentity_t * ent,vec3_t start,vec3_t mins,vec3_t maxs)200 void W_TraceSetStart( gentity_t *ent, vec3_t start, vec3_t mins, vec3_t maxs )
201 //-----------------------------------------------------------------------------
202 {
203 //make sure our start point isn't on the other side of a wall
204 trace_t tr;
205 vec3_t entMins;
206 vec3_t entMaxs;
207 vec3_t eyePoint;
208
209 VectorAdd( ent->r.currentOrigin, ent->r.mins, entMins );
210 VectorAdd( ent->r.currentOrigin, ent->r.maxs, entMaxs );
211
212 if ( G_BoxInBounds( start, mins, maxs, entMins, entMaxs ) )
213 {
214 return;
215 }
216
217 if ( !ent->client )
218 {
219 return;
220 }
221
222 VectorCopy( ent->s.pos.trBase, eyePoint);
223 eyePoint[2] += ent->client->ps.viewheight;
224
225 trap->Trace( &tr, eyePoint, mins, maxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP, qfalse, 0, 0 );
226
227 if ( tr.startsolid || tr.allsolid )
228 {
229 return;
230 }
231
232 if ( tr.fraction < 1.0f )
233 {
234 VectorCopy( tr.endpos, start );
235 }
236 }
237
238
239 /*
240 ----------------------------------------------
241 PLAYER WEAPONS
242 ----------------------------------------------
243 */
244
245 /*
246 ======================================================================
247
248 BRYAR PISTOL
249
250 ======================================================================
251 */
252
253 //----------------------------------------------
WP_FireBryarPistol(gentity_t * ent,qboolean altFire)254 static void WP_FireBryarPistol( gentity_t *ent, qboolean altFire )
255 //---------------------------------------------------------
256 {
257 int damage = BRYAR_PISTOL_DAMAGE;
258 int count;
259
260 gentity_t *missile = CreateMissile( muzzle, forward, BRYAR_PISTOL_VEL, 10000, ent, altFire );
261
262 missile->classname = "bryar_proj";
263 missile->s.weapon = WP_BRYAR_PISTOL;
264
265 if ( altFire )
266 {
267 float boxSize = 0;
268
269 count = ( level.time - ent->client->ps.weaponChargeTime ) / BRYAR_CHARGE_UNIT;
270
271 if ( count < 1 )
272 {
273 count = 1;
274 }
275 else if ( count > 5 )
276 {
277 count = 5;
278 }
279
280 if (count > 1)
281 {
282 damage *= (count*1.7);
283 }
284 else
285 {
286 damage *= (count*1.5);
287 }
288
289 missile->s.generic1 = count; // The missile will then render according to the charge level.
290
291 boxSize = BRYAR_ALT_SIZE*(count*0.5);
292
293 VectorSet( missile->r.maxs, boxSize, boxSize, boxSize );
294 VectorSet( missile->r.mins, -boxSize, -boxSize, -boxSize );
295 }
296
297 missile->damage = damage;
298 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
299 if (altFire)
300 {
301 missile->methodOfDeath = MOD_BRYAR_PISTOL_ALT;
302 }
303 else
304 {
305 missile->methodOfDeath = MOD_BRYAR_PISTOL;
306 }
307 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
308
309 // we don't want it to bounce forever
310 missile->bounceCount = 8;
311 }
312
313 /*
314 ======================================================================
315
316 GENERIC
317
318 ======================================================================
319 */
320
321 //---------------------------------------------------------
WP_FireTurretMissile(gentity_t * ent,vec3_t start,vec3_t dir,qboolean altFire,int damage,int velocity,int mod,gentity_t * ignore)322 void WP_FireTurretMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire, int damage, int velocity, int mod, gentity_t *ignore )
323 //---------------------------------------------------------
324 {
325 gentity_t *missile;
326
327 missile = CreateMissile( start, dir, velocity, 10000, ent, altFire );
328
329 missile->classname = "generic_proj";
330 missile->s.weapon = WP_TURRET;
331
332 missile->damage = damage;
333 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
334 missile->methodOfDeath = mod;
335 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
336
337 if (ignore)
338 {
339 missile->passThroughNum = ignore->s.number+1;
340 }
341
342 // we don't want it to bounce forever
343 missile->bounceCount = 8;
344 }
345
346 //-----------------------------------------------------------------------------
WP_Explode(gentity_t * self)347 void WP_Explode( gentity_t *self )
348 //-----------------------------------------------------------------------------
349 {
350 gentity_t *attacker = self;
351 vec3_t forwardVec={0,0,1};
352
353 // stop chain reaction runaway loops
354 self->takedamage = qfalse;
355
356 self->s.loopSound = 0;
357
358 // VectorCopy( self->currentOrigin, self->s.pos.trBase );
359 if ( !self->client )
360 {
361 AngleVectors( self->s.angles, forwardVec, NULL, NULL );
362 }
363
364 // FIXME
365 /*if ( self->e > 0 )
366 {
367 G_PlayEffect( self->fxID, self->r.currentOrigin, forwardVec );
368 }*/
369
370 if ( self->s.owner && self->s.owner != 1023 )
371 {
372 attacker = &g_entities[self->s.owner];
373 }
374 else if ( self->activator )
375 {
376 attacker = self->activator;
377 }
378 else if ( self->client )
379 {
380 attacker = self;
381 }
382
383 if ( self->splashDamage > 0 && self->splashRadius > 0 )
384 {
385 G_RadiusDamage( self->r.currentOrigin, attacker, self->splashDamage, self->splashRadius, 0/*don't ignore attacker*/, self, MOD_UNKNOWN );
386 }
387
388 if ( self->target )
389 {
390 G_UseTargets( self, attacker );
391 }
392
393 G_SetOrigin( self, self->r.currentOrigin );
394
395 self->nextthink = level.time + 50;
396 self->think = G_FreeEntity;
397 }
398
399 //Currently only the seeker drone uses this, but it might be useful for other things as well.
400
401 //---------------------------------------------------------
WP_FireGenericBlasterMissile(gentity_t * ent,vec3_t start,vec3_t dir,qboolean altFire,int damage,int velocity,int mod)402 void WP_FireGenericBlasterMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire, int damage, int velocity, int mod )
403 //---------------------------------------------------------
404 {
405 gentity_t *missile;
406
407 missile = CreateMissile( start, dir, velocity, 10000, ent, altFire );
408
409 missile->classname = "generic_proj";
410 missile->s.weapon = WP_BRYAR_PISTOL;
411
412 missile->damage = damage;
413 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
414 missile->methodOfDeath = mod;
415 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
416
417 // we don't want it to bounce forever
418 missile->bounceCount = 8;
419 }
420
421 /*
422 ======================================================================
423
424 BLASTER
425
426 ======================================================================
427 */
428
429 //---------------------------------------------------------
WP_FireBlasterMissile(gentity_t * ent,vec3_t start,vec3_t dir,qboolean altFire)430 void WP_FireBlasterMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire )
431 //---------------------------------------------------------
432 {
433 int velocity = BLASTER_VELOCITY;
434 int damage = BLASTER_DAMAGE;
435 gentity_t *missile;
436
437 if (ent->s.eType == ET_NPC)
438 { //animent
439 damage = 10;
440 }
441
442 missile = CreateMissile( start, dir, velocity, 10000, ent, altFire );
443
444 missile->classname = "blaster_proj";
445 missile->s.weapon = WP_BLASTER;
446
447 missile->damage = damage;
448 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
449 missile->methodOfDeath = MOD_BLASTER;
450 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
451
452 // we don't want it to bounce forever
453 missile->bounceCount = 8;
454 }
455
456 //---------------------------------------------------------
WP_FireTurboLaserMissile(gentity_t * ent,vec3_t start,vec3_t dir)457 void WP_FireTurboLaserMissile( gentity_t *ent, vec3_t start, vec3_t dir )
458 //---------------------------------------------------------
459 {
460 int velocity = ent->mass; //FIXME: externalize
461 gentity_t *missile;
462
463 missile = CreateMissile( start, dir, velocity, 10000, ent, qfalse );
464
465 //use a custom shot effect
466 missile->s.otherEntityNum2 = ent->genericValue14;
467 //use a custom impact effect
468 missile->s.emplacedOwner = ent->genericValue15;
469
470 missile->classname = "turbo_proj";
471 missile->s.weapon = WP_TURRET;
472
473 missile->damage = ent->damage; //FIXME: externalize
474 missile->splashDamage = ent->splashDamage; //FIXME: externalize
475 missile->splashRadius = ent->splashRadius; //FIXME: externalize
476 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
477 missile->methodOfDeath = MOD_TURBLAST; //count as a heavy weap
478 missile->splashMethodOfDeath = MOD_TURBLAST;// ?SPLASH;
479 missile->clipmask = MASK_SHOT;
480
481 // we don't want it to bounce forever
482 missile->bounceCount = 8;
483
484 //set veh as cgame side owner for purpose of fx overrides
485 missile->s.owner = ent->s.number;
486
487 //don't let them last forever
488 missile->think = G_FreeEntity;
489 missile->nextthink = level.time + 5000;//at 20000 speed, that should be more than enough
490 }
491
492 //---------------------------------------------------------
WP_FireEmplacedMissile(gentity_t * ent,vec3_t start,vec3_t dir,qboolean altFire,gentity_t * ignore)493 void WP_FireEmplacedMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire, gentity_t *ignore )
494 //---------------------------------------------------------
495 {
496 int velocity = BLASTER_VELOCITY;
497 int damage = BLASTER_DAMAGE;
498 gentity_t *missile;
499
500 missile = CreateMissile( start, dir, velocity, 10000, ent, altFire );
501
502 missile->classname = "emplaced_gun_proj";
503 missile->s.weapon = WP_TURRET;//WP_EMPLACED_GUN;
504
505 missile->activator = ignore;
506
507 missile->damage = damage;
508 missile->dflags = (DAMAGE_DEATH_KNOCKBACK|DAMAGE_HEAVY_WEAP_CLASS);
509 missile->methodOfDeath = MOD_VEHICLE;
510 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
511
512 if (ignore)
513 {
514 missile->passThroughNum = ignore->s.number+1;
515 }
516
517 // we don't want it to bounce forever
518 missile->bounceCount = 8;
519 }
520
521 //---------------------------------------------------------
WP_FireBlaster(gentity_t * ent,qboolean altFire)522 static void WP_FireBlaster( gentity_t *ent, qboolean altFire )
523 //---------------------------------------------------------
524 {
525 vec3_t dir, angs;
526
527 vectoangles( forward, angs );
528
529 if ( altFire )
530 {
531 // add some slop to the alt-fire direction
532 angs[PITCH] += Q_flrand(-1.0f, 1.0f) * BLASTER_SPREAD;
533 angs[YAW] += Q_flrand(-1.0f, 1.0f) * BLASTER_SPREAD;
534 }
535
536 AngleVectors( angs, dir, NULL, NULL );
537
538 // FIXME: if temp_org does not have clear trace to inside the bbox, don't shoot!
539 WP_FireBlasterMissile( ent, muzzle, dir, altFire );
540 }
541
542
543 int G_GetHitLocation(gentity_t *target, vec3_t ppoint);
544
545 /*
546 ======================================================================
547
548 DISRUPTOR
549
550 ======================================================================
551 */
552 //---------------------------------------------------------
WP_DisruptorMainFire(gentity_t * ent)553 static void WP_DisruptorMainFire( gentity_t *ent )
554 //---------------------------------------------------------
555 {
556 int damage = DISRUPTOR_MAIN_DAMAGE;
557 qboolean render_impact = qtrue;
558 vec3_t start, end;
559 trace_t tr;
560 gentity_t *traceEnt, *tent;
561 float shotRange = 8192;
562 int ignore, traces;
563
564 if ( level.gametype == GT_SIEGE )
565 {
566 damage = DISRUPTOR_MAIN_DAMAGE_SIEGE;
567 }
568
569 memset(&tr, 0, sizeof(tr)); //to shut the compiler up
570
571 VectorCopy( ent->client->ps.origin, start );
572 start[2] += ent->client->ps.viewheight;//By eyes
573
574 VectorMA( start, shotRange, forward, end );
575
576 ignore = ent->s.number;
577 traces = 0;
578 while ( traces < 10 )
579 {//need to loop this in case we hit a Jedi who dodges the shot
580 if (d_projectileGhoul2Collision.integer)
581 {
582 trap->Trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT, qfalse, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
583 }
584 else
585 {
586 trap->Trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT, qfalse, 0, 0 );
587 }
588
589 traceEnt = &g_entities[tr.entityNum];
590
591 if (d_projectileGhoul2Collision.integer && traceEnt->inuse && traceEnt->client)
592 { //g2 collision checks -rww
593 if (traceEnt->inuse && traceEnt->client && traceEnt->ghoul2)
594 { //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with.
595 traceEnt->client->g2LastSurfaceHit = tr.surfaceFlags;
596 traceEnt->client->g2LastSurfaceTime = level.time;
597 }
598
599 if (traceEnt->ghoul2)
600 {
601 tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here.
602 }
603 }
604
605 if (traceEnt && traceEnt->client && traceEnt->client->ps.duelInProgress &&
606 traceEnt->client->ps.duelIndex != ent->s.number)
607 {
608 VectorCopy( tr.endpos, start );
609 ignore = tr.entityNum;
610 traces++;
611 continue;
612 }
613
614 if ( Jedi_DodgeEvasion( traceEnt, ent, &tr, G_GetHitLocation(traceEnt, tr.endpos) ) )
615 {//act like we didn't even hit him
616 VectorCopy( tr.endpos, start );
617 ignore = tr.entityNum;
618 traces++;
619 continue;
620 }
621 else if (traceEnt && traceEnt->client && traceEnt->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_3)
622 {
623 if (WP_SaberCanBlock(traceEnt, tr.endpos, 0, MOD_DISRUPTOR, qtrue, 0))
624 { //broadcast and stop the shot because it was blocked
625 gentity_t *te = NULL;
626
627 tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT );
628 VectorCopy( muzzle, tent->s.origin2 );
629 tent->s.eventParm = ent->s.number;
630
631 te = G_TempEntity( tr.endpos, EV_SABER_BLOCK );
632 VectorCopy(tr.endpos, te->s.origin);
633 VectorCopy(tr.plane.normal, te->s.angles);
634 if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
635 {
636 te->s.angles[1] = 1;
637 }
638 te->s.eventParm = 0;
639 te->s.weapon = 0;//saberNum
640 te->s.legsAnim = 0;//bladeNum
641
642 return;
643 }
644 }
645 else if ( (traceEnt->flags&FL_SHIELDED) )
646 {//stopped cold
647 return;
648 }
649 //a Jedi is not dodging this shot
650 break;
651 }
652
653 if ( tr.surfaceFlags & SURF_NOIMPACT )
654 {
655 render_impact = qfalse;
656 }
657
658 // always render a shot beam, doing this the old way because I don't much feel like overriding the effect.
659 tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT );
660 VectorCopy( muzzle, tent->s.origin2 );
661 tent->s.eventParm = ent->s.number;
662
663 traceEnt = &g_entities[tr.entityNum];
664
665 if ( render_impact )
666 {
667 if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
668 {
669 if ( traceEnt->client && LogAccuracyHit( traceEnt, ent ))
670 {
671 ent->client->accuracy_hits++;
672 }
673
674 G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NORMAL, MOD_DISRUPTOR );
675
676 tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_HIT );
677 tent->s.eventParm = DirToByte( tr.plane.normal );
678 if (traceEnt->client)
679 {
680 tent->s.weapon = 1;
681 }
682 }
683 else
684 {
685 // Hmmm, maybe don't make any marks on things that could break
686 tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_MISS );
687 tent->s.eventParm = DirToByte( tr.plane.normal );
688 tent->s.weapon = 1;
689 }
690 }
691 }
692
693
G_CanDisruptify(gentity_t * ent)694 qboolean G_CanDisruptify(gentity_t *ent)
695 {
696 if (!ent || !ent->inuse || !ent->client || ent->s.eType != ET_NPC ||
697 ent->s.NPC_class != CLASS_VEHICLE || !ent->m_pVehicle)
698 { //not vehicle
699 return qtrue;
700 }
701
702 if (ent->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL)
703 { //animal is only type that can be disintigeiteigerated
704 return qtrue;
705 }
706
707 //don't do it to any other veh
708 return qfalse;
709 }
710
711 //---------------------------------------------------------
WP_DisruptorAltFire(gentity_t * ent)712 void WP_DisruptorAltFire( gentity_t *ent )
713 //---------------------------------------------------------
714 {
715 int damage = 0, skip;
716 qboolean render_impact = qtrue;
717 vec3_t start, end;
718 //vec3_t muzzle2;
719 trace_t tr;
720 gentity_t *traceEnt, *tent;
721 float shotRange = 8192.0f;
722 int i;
723 int count, maxCount = 60;
724 int traces = DISRUPTOR_ALT_TRACES;
725 qboolean fullCharge = qfalse;
726
727 damage = DISRUPTOR_ALT_DAMAGE-30;
728
729 //VectorCopy( muzzle, muzzle2 ); // making a backup copy
730
731 if (ent->client)
732 {
733 VectorCopy( ent->client->ps.origin, start );
734 start[2] += ent->client->ps.viewheight;//By eyes
735
736 count = ( level.time - ent->client->ps.weaponChargeTime ) / DISRUPTOR_CHARGE_UNIT;
737 if ( level.gametype == GT_SIEGE )
738 {//maybe a full alt-charge should be a *bit* more dangerous in Siege mode?
739 //maxCount = ceil((200.0f-(float)damage)/2.0f);//cap at 200 damage total
740 maxCount = 200;//the previous line ALWAYS evaluated to 135 - was that on purpose?
741 }
742 }
743 else
744 {
745 VectorCopy( ent->r.currentOrigin, start );
746 start[2] += 24;
747
748 count = ( 100 ) / DISRUPTOR_CHARGE_UNIT;
749 }
750
751 count *= 2;
752
753 if ( count < 1 )
754 {
755 count = 1;
756 }
757 else if ( count >= maxCount )
758 {
759 count = maxCount;
760 fullCharge = qtrue;
761 }
762
763 // more powerful charges go through more things
764 if ( count < 10 )
765 {
766 traces = 1;
767 }
768 else if ( count < 20 )
769 {
770 traces = 2;
771 }
772
773 damage += count;
774
775 skip = ent->s.number;
776
777 for (i = 0; i < traces; i++ )
778 {
779 VectorMA( start, shotRange, forward, end );
780
781 if (d_projectileGhoul2Collision.integer)
782 {
783 trap->Trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT, qfalse, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
784 }
785 else
786 {
787 trap->Trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT, qfalse, 0, 0 );
788 }
789
790 if ( tr.entityNum == ent->s.number )
791 {
792 // should never happen, but basically we don't want to consider a hit to ourselves?
793 // Get ready for an attempt to trace through another person
794 //VectorCopy( tr.endpos, muzzle2 );
795 VectorCopy( tr.endpos, start );
796 skip = tr.entityNum;
797 #ifdef _DEBUG
798 trap->Print( "BAD! Disruptor gun shot somehow traced back and hit the owner!\n" );
799 #endif
800 continue;
801 }
802
803 traceEnt = &g_entities[tr.entityNum];
804
805 if (d_projectileGhoul2Collision.integer && traceEnt->inuse && traceEnt->client)
806 { //g2 collision checks -rww
807 if (traceEnt->inuse && traceEnt->client && traceEnt->ghoul2)
808 { //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with.
809 traceEnt->client->g2LastSurfaceHit = tr.surfaceFlags;
810 traceEnt->client->g2LastSurfaceTime = level.time;
811 }
812
813 if (traceEnt->ghoul2)
814 {
815 tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here.
816 }
817 }
818
819 if ( tr.surfaceFlags & SURF_NOIMPACT )
820 {
821 render_impact = qfalse;
822 }
823
824 if (traceEnt && traceEnt->client && traceEnt->client->ps.duelInProgress &&
825 traceEnt->client->ps.duelIndex != ent->s.number)
826 {
827 skip = tr.entityNum;
828 VectorCopy(tr.endpos, start);
829 continue;
830 }
831
832 if (Jedi_DodgeEvasion(traceEnt, ent, &tr, G_GetHitLocation(traceEnt, tr.endpos)))
833 {
834 skip = tr.entityNum;
835 VectorCopy(tr.endpos, start);
836 continue;
837 }
838 else if (traceEnt && traceEnt->client && traceEnt->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_3)
839 {
840 if (WP_SaberCanBlock(traceEnt, tr.endpos, 0, MOD_DISRUPTOR_SNIPER, qtrue, 0))
841 { //broadcast and stop the shot because it was blocked
842 gentity_t *te = NULL;
843
844 tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_SHOT );
845 VectorCopy( muzzle, tent->s.origin2 );
846 tent->s.shouldtarget = fullCharge;
847 tent->s.eventParm = ent->s.number;
848
849 te = G_TempEntity( tr.endpos, EV_SABER_BLOCK );
850 VectorCopy(tr.endpos, te->s.origin);
851 VectorCopy(tr.plane.normal, te->s.angles);
852 if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
853 {
854 te->s.angles[1] = 1;
855 }
856 te->s.eventParm = 0;
857 te->s.weapon = 0;//saberNum
858 te->s.legsAnim = 0;//bladeNum
859
860 return;
861 }
862 }
863
864 // always render a shot beam, doing this the old way because I don't much feel like overriding the effect.
865 tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_SHOT );
866 VectorCopy( muzzle, tent->s.origin2 );
867 tent->s.shouldtarget = fullCharge;
868 tent->s.eventParm = ent->s.number;
869
870 // If the beam hits a skybox, etc. it would look foolish to add impact effects
871 if ( render_impact )
872 {
873 if ( traceEnt->takedamage && traceEnt->client )
874 {
875 tent->s.otherEntityNum = traceEnt->s.number;
876
877 // Create a simple impact type mark
878 tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS);
879 tent->s.eventParm = DirToByte(tr.plane.normal);
880 tent->s.eFlags |= EF_ALT_FIRING;
881
882 if ( LogAccuracyHit( traceEnt, ent ))
883 {
884 if (ent->client)
885 {
886 ent->client->accuracy_hits++;
887 }
888 }
889 }
890 else
891 {
892 if ( traceEnt->r.svFlags & SVF_GLASS_BRUSH
893 || traceEnt->takedamage
894 || traceEnt->s.eType == ET_MOVER )
895 {
896 if ( traceEnt->takedamage )
897 {
898 G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage,
899 DAMAGE_NO_KNOCKBACK, MOD_DISRUPTOR_SNIPER );
900
901 tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_HIT );
902 tent->s.eventParm = DirToByte( tr.plane.normal );
903 }
904 }
905 else
906 {
907 // Hmmm, maybe don't make any marks on things that could break
908 tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_MISS );
909 tent->s.eventParm = DirToByte( tr.plane.normal );
910 }
911 break; // and don't try any more traces
912 }
913
914 if ( (traceEnt->flags&FL_SHIELDED) )
915 {//stops us cold
916 break;
917 }
918
919 if ( traceEnt->takedamage )
920 {
921 vec3_t preAng;
922 int preHealth = traceEnt->health;
923 int preLegs = 0;
924 int preTorso = 0;
925
926 if (traceEnt->client)
927 {
928 preLegs = traceEnt->client->ps.legsAnim;
929 preTorso = traceEnt->client->ps.torsoAnim;
930 VectorCopy(traceEnt->client->ps.viewangles, preAng);
931 }
932
933 G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_DISRUPTOR_SNIPER );
934
935 if (traceEnt->client && preHealth > 0 && traceEnt->health <= 0 && fullCharge &&
936 G_CanDisruptify(traceEnt))
937 { //was killed by a fully charged sniper shot, so disintegrate
938 VectorCopy(preAng, traceEnt->client->ps.viewangles);
939
940 traceEnt->client->ps.eFlags |= EF_DISINTEGRATION;
941 VectorCopy(tr.endpos, traceEnt->client->ps.lastHitLoc);
942
943 traceEnt->client->ps.legsAnim = preLegs;
944 traceEnt->client->ps.torsoAnim = preTorso;
945
946 traceEnt->r.contents = 0;
947
948 VectorClear(traceEnt->client->ps.velocity);
949 }
950
951 tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_HIT );
952 tent->s.eventParm = DirToByte( tr.plane.normal );
953 if (traceEnt->client)
954 {
955 tent->s.weapon = 1;
956 }
957 }
958 }
959 else // not rendering impact, must be a skybox or other similar thing?
960 {
961 break; // don't try anymore traces
962 }
963
964 // Get ready for an attempt to trace through another person
965 VectorCopy( tr.endpos, muzzle );
966 VectorCopy( tr.endpos, start );
967 skip = tr.entityNum;
968 }
969 }
970
971
972 //---------------------------------------------------------
WP_FireDisruptor(gentity_t * ent,qboolean altFire)973 static void WP_FireDisruptor( gentity_t *ent, qboolean altFire )
974 //---------------------------------------------------------
975 {
976 if (!ent || !ent->client || ent->client->ps.zoomMode != 1)
977 { //do not ever let it do the alt fire when not zoomed
978 altFire = qfalse;
979 }
980
981 if (ent && ent->s.eType == ET_NPC && !ent->client)
982 { //special case for animents
983 WP_DisruptorAltFire( ent );
984 return;
985 }
986
987 if ( altFire )
988 {
989 WP_DisruptorAltFire( ent );
990 }
991 else
992 {
993 WP_DisruptorMainFire( ent );
994 }
995 }
996
997
998 /*
999 ======================================================================
1000
1001 BOWCASTER
1002
1003 ======================================================================
1004 */
1005
WP_BowcasterAltFire(gentity_t * ent)1006 static void WP_BowcasterAltFire( gentity_t *ent )
1007 {
1008 int damage = BOWCASTER_DAMAGE;
1009
1010 gentity_t *missile = CreateMissile( muzzle, forward, BOWCASTER_VELOCITY, 10000, ent, qfalse);
1011
1012 missile->classname = "bowcaster_proj";
1013 missile->s.weapon = WP_BOWCASTER;
1014
1015 VectorSet( missile->r.maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE );
1016 VectorScale( missile->r.maxs, -1, missile->r.mins );
1017
1018 missile->damage = damage;
1019 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1020 missile->methodOfDeath = MOD_BOWCASTER;
1021 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1022
1023 missile->flags |= FL_BOUNCE;
1024 missile->bounceCount = 3;
1025 }
1026
1027 //---------------------------------------------------------
WP_BowcasterMainFire(gentity_t * ent)1028 static void WP_BowcasterMainFire( gentity_t *ent )
1029 //---------------------------------------------------------
1030 {
1031 int damage = BOWCASTER_DAMAGE, count;
1032 float vel;
1033 vec3_t angs, dir;
1034 gentity_t *missile;
1035 int i;
1036
1037 if (!ent->client)
1038 {
1039 count = 1;
1040 }
1041 else
1042 {
1043 count = ( level.time - ent->client->ps.weaponChargeTime ) / BOWCASTER_CHARGE_UNIT;
1044 }
1045
1046 if ( count < 1 )
1047 {
1048 count = 1;
1049 }
1050 else if ( count > 5 )
1051 {
1052 count = 5;
1053 }
1054
1055 if ( !(count & 1 ))
1056 {
1057 // if we aren't odd, knock us down a level
1058 count--;
1059 }
1060
1061 //scale the damage down based on how many are about to be fired
1062 if (count <= 1)
1063 {
1064 damage = 50;
1065 }
1066 else if (count == 2)
1067 {
1068 damage = 45;
1069 }
1070 else if (count == 3)
1071 {
1072 damage = 40;
1073 }
1074 else if (count == 4)
1075 {
1076 damage = 35;
1077 }
1078 else
1079 {
1080 damage = 30;
1081 }
1082
1083 for (i = 0; i < count; i++ )
1084 {
1085 // create a range of different velocities
1086 vel = BOWCASTER_VELOCITY * ( Q_flrand(-1.0f, 1.0f) * BOWCASTER_VEL_RANGE + 1.0f );
1087
1088 vectoangles( forward, angs );
1089
1090 // add some slop to the alt-fire direction
1091 angs[PITCH] += Q_flrand(-1.0f, 1.0f) * BOWCASTER_ALT_SPREAD * 0.2f;
1092 angs[YAW] += ((i+0.5f) * BOWCASTER_ALT_SPREAD - count * 0.5f * BOWCASTER_ALT_SPREAD );
1093
1094 AngleVectors( angs, dir, NULL, NULL );
1095
1096 missile = CreateMissile( muzzle, dir, vel, 10000, ent, qtrue );
1097
1098 missile->classname = "bowcaster_alt_proj";
1099 missile->s.weapon = WP_BOWCASTER;
1100
1101 VectorSet( missile->r.maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE );
1102 VectorScale( missile->r.maxs, -1, missile->r.mins );
1103
1104 missile->damage = damage;
1105 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1106 missile->methodOfDeath = MOD_BOWCASTER;
1107 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1108
1109 // we don't want it to bounce
1110 missile->bounceCount = 0;
1111 }
1112 }
1113
1114 //---------------------------------------------------------
WP_FireBowcaster(gentity_t * ent,qboolean altFire)1115 static void WP_FireBowcaster( gentity_t *ent, qboolean altFire )
1116 //---------------------------------------------------------
1117 {
1118 if ( altFire )
1119 {
1120 WP_BowcasterAltFire( ent );
1121 }
1122 else
1123 {
1124 WP_BowcasterMainFire( ent );
1125 }
1126 }
1127
1128
1129
1130 /*
1131 ======================================================================
1132
1133 REPEATER
1134
1135 ======================================================================
1136 */
1137
1138 //---------------------------------------------------------
WP_RepeaterMainFire(gentity_t * ent,vec3_t dir)1139 static void WP_RepeaterMainFire( gentity_t *ent, vec3_t dir )
1140 //---------------------------------------------------------
1141 {
1142 int damage = REPEATER_DAMAGE;
1143
1144 gentity_t *missile = CreateMissile( muzzle, dir, REPEATER_VELOCITY, 10000, ent, qfalse );
1145
1146 missile->classname = "repeater_proj";
1147 missile->s.weapon = WP_REPEATER;
1148
1149 missile->damage = damage;
1150 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1151 missile->methodOfDeath = MOD_REPEATER;
1152 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1153
1154 // we don't want it to bounce forever
1155 missile->bounceCount = 8;
1156 }
1157
1158 //---------------------------------------------------------
WP_RepeaterAltFire(gentity_t * ent)1159 static void WP_RepeaterAltFire( gentity_t *ent )
1160 //---------------------------------------------------------
1161 {
1162 int damage = REPEATER_ALT_DAMAGE;
1163
1164 gentity_t *missile = CreateMissile( muzzle, forward, REPEATER_ALT_VELOCITY, 10000, ent, qtrue );
1165
1166 missile->classname = "repeater_alt_proj";
1167 missile->s.weapon = WP_REPEATER;
1168
1169 VectorSet( missile->r.maxs, REPEATER_ALT_SIZE, REPEATER_ALT_SIZE, REPEATER_ALT_SIZE );
1170 VectorScale( missile->r.maxs, -1, missile->r.mins );
1171 missile->s.pos.trType = TR_GRAVITY;
1172 missile->s.pos.trDelta[2] += 40.0f; //give a slight boost in the upward direction
1173 missile->damage = damage;
1174 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1175 missile->methodOfDeath = MOD_REPEATER_ALT;
1176 missile->splashMethodOfDeath = MOD_REPEATER_ALT_SPLASH;
1177 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1178 missile->splashDamage = REPEATER_ALT_SPLASH_DAMAGE;
1179 if ( level.gametype == GT_SIEGE ) // we've been having problems with this being too hyper-potent because of it's radius
1180 {
1181 missile->splashRadius = REPEATER_ALT_SPLASH_RAD_SIEGE;
1182 }
1183 else
1184 {
1185 missile->splashRadius = REPEATER_ALT_SPLASH_RADIUS;
1186 }
1187
1188 // we don't want it to bounce forever
1189 missile->bounceCount = 8;
1190 }
1191
1192 //---------------------------------------------------------
WP_FireRepeater(gentity_t * ent,qboolean altFire)1193 static void WP_FireRepeater( gentity_t *ent, qboolean altFire )
1194 //---------------------------------------------------------
1195 {
1196 vec3_t dir, angs;
1197
1198 vectoangles( forward, angs );
1199
1200 if ( altFire )
1201 {
1202 WP_RepeaterAltFire( ent );
1203 }
1204 else
1205 {
1206 // add some slop to the alt-fire direction
1207 angs[PITCH] += Q_flrand(-1.0f, 1.0f) * REPEATER_SPREAD;
1208 angs[YAW] += Q_flrand(-1.0f, 1.0f) * REPEATER_SPREAD;
1209
1210 AngleVectors( angs, dir, NULL, NULL );
1211
1212 WP_RepeaterMainFire( ent, dir );
1213 }
1214 }
1215
1216
1217 /*
1218 ======================================================================
1219
1220 DEMP2
1221
1222 ======================================================================
1223 */
1224
WP_DEMP2_MainFire(gentity_t * ent)1225 static void WP_DEMP2_MainFire( gentity_t *ent )
1226 {
1227 int damage = DEMP2_DAMAGE;
1228
1229 gentity_t *missile = CreateMissile( muzzle, forward, DEMP2_VELOCITY, 10000, ent, qfalse);
1230
1231 missile->classname = "demp2_proj";
1232 missile->s.weapon = WP_DEMP2;
1233
1234 VectorSet( missile->r.maxs, DEMP2_SIZE, DEMP2_SIZE, DEMP2_SIZE );
1235 VectorScale( missile->r.maxs, -1, missile->r.mins );
1236 missile->damage = damage;
1237 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1238 missile->methodOfDeath = MOD_DEMP2;
1239 missile->clipmask = MASK_SHOT;
1240
1241 // we don't want it to ever bounce
1242 missile->bounceCount = 0;
1243 }
1244
1245 static gentity_t *ent_list[MAX_GENTITIES];
1246
DEMP2_AltRadiusDamage(gentity_t * ent)1247 void DEMP2_AltRadiusDamage( gentity_t *ent )
1248 {
1249 float frac = ( level.time - ent->genericValue5 ) / 800.0f; // / 1600.0f; // synchronize with demp2 effect
1250 float dist, radius, fact;
1251 gentity_t *gent;
1252 int iEntityList[MAX_GENTITIES];
1253 gentity_t *entityList[MAX_GENTITIES];
1254 gentity_t *myOwner = NULL;
1255 int numListedEntities, i, e;
1256 vec3_t mins, maxs;
1257 vec3_t v, dir;
1258
1259 if (ent->r.ownerNum >= 0 &&
1260 ent->r.ownerNum < /*MAX_CLIENTS ... let npc's/shooters use it*/MAX_GENTITIES)
1261 {
1262 myOwner = &g_entities[ent->r.ownerNum];
1263 }
1264
1265 if (!myOwner || !myOwner->inuse || !myOwner->client)
1266 {
1267 ent->think = G_FreeEntity;
1268 ent->nextthink = level.time;
1269 return;
1270 }
1271
1272 frac *= frac * frac; // yes, this is completely ridiculous...but it causes the shell to grow slowly then "explode" at the end
1273
1274 radius = frac * 200.0f; // 200 is max radius...the model is aprox. 100 units tall...the fx draw code mults. this by 2.
1275
1276 fact = ent->count*0.6;
1277
1278 if (fact < 1)
1279 {
1280 fact = 1;
1281 }
1282
1283 radius *= fact;
1284
1285 for ( i = 0 ; i < 3 ; i++ )
1286 {
1287 mins[i] = ent->r.currentOrigin[i] - radius;
1288 maxs[i] = ent->r.currentOrigin[i] + radius;
1289 }
1290
1291 numListedEntities = trap->EntitiesInBox( mins, maxs, iEntityList, MAX_GENTITIES );
1292
1293 i = 0;
1294 while (i < numListedEntities)
1295 {
1296 entityList[i] = &g_entities[iEntityList[i]];
1297 i++;
1298 }
1299
1300 for ( e = 0 ; e < numListedEntities ; e++ )
1301 {
1302 gent = entityList[ e ];
1303
1304 if ( !gent || !gent->takedamage || !gent->r.contents )
1305 {
1306 continue;
1307 }
1308
1309 // find the distance from the edge of the bounding box
1310 for ( i = 0 ; i < 3 ; i++ )
1311 {
1312 if ( ent->r.currentOrigin[i] < gent->r.absmin[i] )
1313 {
1314 v[i] = gent->r.absmin[i] - ent->r.currentOrigin[i];
1315 }
1316 else if ( ent->r.currentOrigin[i] > gent->r.absmax[i] )
1317 {
1318 v[i] = ent->r.currentOrigin[i] - gent->r.absmax[i];
1319 }
1320 else
1321 {
1322 v[i] = 0;
1323 }
1324 }
1325
1326 // shape is an ellipsoid, so cut vertical distance in half`
1327 v[2] *= 0.5f;
1328
1329 dist = VectorLength( v );
1330
1331 if ( dist >= radius )
1332 {
1333 // shockwave hasn't hit them yet
1334 continue;
1335 }
1336
1337 if (dist+(16*ent->count) < ent->genericValue6)
1338 {
1339 // shockwave has already hit this thing...
1340 continue;
1341 }
1342
1343 VectorCopy( gent->r.currentOrigin, v );
1344 VectorSubtract( v, ent->r.currentOrigin, dir);
1345
1346 // push the center of mass higher than the origin so players get knocked into the air more
1347 dir[2] += 12;
1348
1349 if (gent != myOwner)
1350 {
1351 G_Damage( gent, myOwner, myOwner, dir, ent->r.currentOrigin, ent->damage, DAMAGE_DEATH_KNOCKBACK, ent->splashMethodOfDeath );
1352 if ( gent->takedamage
1353 && gent->client )
1354 {
1355 if ( gent->client->ps.electrifyTime < level.time )
1356 {//electrocution effect
1357 if (gent->s.eType == ET_NPC && gent->s.NPC_class == CLASS_VEHICLE &&
1358 gent->m_pVehicle && (gent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER || gent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER))
1359 { //do some extra stuff to speeders/walkers
1360 gent->client->ps.electrifyTime = level.time + Q_irand( 3000, 4000 );
1361 }
1362 else if ( gent->s.NPC_class != CLASS_VEHICLE
1363 || (gent->m_pVehicle && gent->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER) )
1364 {//don't do this to fighters
1365 gent->client->ps.electrifyTime = level.time + Q_irand( 300, 800 );
1366 }
1367 }
1368 if ( gent->client->ps.powerups[PW_CLOAKED] )
1369 {//disable cloak temporarily
1370 Jedi_Decloak( gent );
1371 gent->client->cloakToggleTime = level.time + Q_irand( 3000, 10000 );
1372 }
1373 }
1374 }
1375 }
1376
1377 // store the last fraction so that next time around we can test against those things that fall between that last point and where the current shockwave edge is
1378 ent->genericValue6 = radius;
1379
1380 if ( frac < 1.0f )
1381 {
1382 // shock is still happening so continue letting it expand
1383 ent->nextthink = level.time + 50;
1384 }
1385 else
1386 { //don't just leave the entity around
1387 ent->think = G_FreeEntity;
1388 ent->nextthink = level.time;
1389 }
1390 }
1391
1392 //---------------------------------------------------------
DEMP2_AltDetonate(gentity_t * ent)1393 void DEMP2_AltDetonate( gentity_t *ent )
1394 //---------------------------------------------------------
1395 {
1396 gentity_t *efEnt;
1397
1398 G_SetOrigin( ent, ent->r.currentOrigin );
1399 if (!ent->pos1[0] && !ent->pos1[1] && !ent->pos1[2])
1400 { //don't play effect with a 0'd out directional vector
1401 ent->pos1[1] = 1;
1402 }
1403 //Let's just save ourself some bandwidth and play both the effect and sphere spawn in 1 event
1404 efEnt = G_PlayEffect( EFFECT_EXPLOSION_DEMP2ALT, ent->r.currentOrigin, ent->pos1 );
1405
1406 if (efEnt)
1407 {
1408 efEnt->s.weapon = ent->count*2;
1409 }
1410
1411 ent->genericValue5 = level.time;
1412 ent->genericValue6 = 0;
1413 ent->nextthink = level.time + 50;
1414 ent->think = DEMP2_AltRadiusDamage;
1415 ent->s.eType = ET_GENERAL; // make us a missile no longer
1416 }
1417
1418 //---------------------------------------------------------
WP_DEMP2_AltFire(gentity_t * ent)1419 static void WP_DEMP2_AltFire( gentity_t *ent )
1420 //---------------------------------------------------------
1421 {
1422 int damage = DEMP2_ALT_DAMAGE;
1423 int count, origcount;
1424 float fact;
1425 vec3_t start, end;
1426 trace_t tr;
1427 gentity_t *missile;
1428
1429 VectorCopy( muzzle, start );
1430
1431 VectorMA( start, DEMP2_ALT_RANGE, forward, end );
1432
1433 count = ( level.time - ent->client->ps.weaponChargeTime ) / DEMP2_CHARGE_UNIT;
1434
1435 origcount = count;
1436
1437 if ( count < 1 )
1438 {
1439 count = 1;
1440 }
1441 else if ( count > 3 )
1442 {
1443 count = 3;
1444 }
1445
1446 fact = count*0.8;
1447 if (fact < 1)
1448 {
1449 fact = 1;
1450 }
1451 damage *= fact;
1452
1453 if (!origcount)
1454 { //this was just a tap-fire
1455 damage = 1;
1456 }
1457
1458 trap->Trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_SHOT, qfalse, 0, 0);
1459
1460 missile = G_Spawn();
1461 G_SetOrigin(missile, tr.endpos);
1462 //In SP the impact actually travels as a missile based on the trace fraction, but we're
1463 //just going to be instant. -rww
1464
1465 VectorCopy( tr.plane.normal, missile->pos1 );
1466
1467 missile->count = count;
1468
1469 missile->classname = "demp2_alt_proj";
1470 missile->s.weapon = WP_DEMP2;
1471
1472 missile->think = DEMP2_AltDetonate;
1473 missile->nextthink = level.time;
1474
1475 missile->splashDamage = missile->damage = damage;
1476 missile->splashMethodOfDeath = missile->methodOfDeath = MOD_DEMP2;
1477 missile->splashRadius = DEMP2_ALT_SPLASHRADIUS;
1478
1479 missile->r.ownerNum = ent->s.number;
1480
1481 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1482 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1483
1484 // we don't want it to ever bounce
1485 missile->bounceCount = 0;
1486 }
1487
1488 //---------------------------------------------------------
WP_FireDEMP2(gentity_t * ent,qboolean altFire)1489 static void WP_FireDEMP2( gentity_t *ent, qboolean altFire )
1490 //---------------------------------------------------------
1491 {
1492 if ( altFire )
1493 {
1494 WP_DEMP2_AltFire( ent );
1495 }
1496 else
1497 {
1498 WP_DEMP2_MainFire( ent );
1499 }
1500 }
1501
1502
1503
1504 /*
1505 ======================================================================
1506
1507 FLECHETTE
1508
1509 ======================================================================
1510 */
1511
1512 //---------------------------------------------------------
WP_FlechetteMainFire(gentity_t * ent)1513 static void WP_FlechetteMainFire( gentity_t *ent )
1514 //---------------------------------------------------------
1515 {
1516 vec3_t fwd, angs;
1517 gentity_t *missile;
1518 int i;
1519
1520 for (i = 0; i < FLECHETTE_SHOTS; i++ )
1521 {
1522 vectoangles( forward, angs );
1523
1524 if (i != 0)
1525 { //do nothing on the first shot, it will hit the crosshairs
1526 angs[PITCH] += Q_flrand(-1.0f, 1.0f) * FLECHETTE_SPREAD;
1527 angs[YAW] += Q_flrand(-1.0f, 1.0f) * FLECHETTE_SPREAD;
1528 }
1529
1530 AngleVectors( angs, fwd, NULL, NULL );
1531
1532 missile = CreateMissile( muzzle, fwd, FLECHETTE_VEL, 10000, ent, qfalse);
1533
1534 missile->classname = "flech_proj";
1535 missile->s.weapon = WP_FLECHETTE;
1536
1537 VectorSet( missile->r.maxs, FLECHETTE_SIZE, FLECHETTE_SIZE, FLECHETTE_SIZE );
1538 VectorScale( missile->r.maxs, -1, missile->r.mins );
1539
1540 missile->damage = FLECHETTE_DAMAGE;
1541 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1542 missile->methodOfDeath = MOD_FLECHETTE;
1543 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1544
1545 // we don't want it to bounce forever
1546 missile->bounceCount = Q_irand(5,8);
1547
1548 missile->flags |= FL_BOUNCE_SHRAPNEL;
1549 }
1550 }
1551
1552 //---------------------------------------------------------
prox_mine_think(gentity_t * ent)1553 void prox_mine_think( gentity_t *ent )
1554 //---------------------------------------------------------
1555 {
1556 int count, i;
1557 qboolean blow = qfalse;
1558
1559 // if it isn't time to auto-explode, do a small proximity check
1560 if ( ent->delay > level.time )
1561 {
1562 count = G_RadiusList( ent->r.currentOrigin, FLECHETTE_MINE_RADIUS_CHECK, ent, qtrue, ent_list );
1563
1564 for ( i = 0; i < count; i++ )
1565 {
1566 if ( ent_list[i]->client && ent_list[i]->health > 0 && ent->activator && ent_list[i]->s.number != ent->activator->s.number )
1567 {
1568 blow = qtrue;
1569 break;
1570 }
1571 }
1572 }
1573 else
1574 {
1575 // well, we must die now
1576 blow = qtrue;
1577 }
1578
1579 if ( blow )
1580 {
1581 ent->think = laserTrapExplode;
1582 ent->nextthink = level.time + 200;
1583 }
1584 else
1585 {
1586 // we probably don't need to do this thinking logic very often...maybe this is fast enough?
1587 ent->nextthink = level.time + 500;
1588 }
1589 }
1590
1591 //-----------------------------------------------------------------------------
WP_TraceSetStart(gentity_t * ent,vec3_t start,vec3_t mins,vec3_t maxs)1592 static void WP_TraceSetStart( gentity_t *ent, vec3_t start, vec3_t mins, vec3_t maxs )
1593 //-----------------------------------------------------------------------------
1594 {
1595 //make sure our start point isn't on the other side of a wall
1596 trace_t tr;
1597 vec3_t entMins;
1598 vec3_t entMaxs;
1599
1600 VectorAdd( ent->r.currentOrigin, ent->r.mins, entMins );
1601 VectorAdd( ent->r.currentOrigin, ent->r.maxs, entMaxs );
1602
1603 if ( G_BoxInBounds( start, mins, maxs, entMins, entMaxs ) )
1604 {
1605 return;
1606 }
1607
1608 if ( !ent->client )
1609 {
1610 return;
1611 }
1612
1613 trap->Trace( &tr, ent->client->ps.origin, mins, maxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP, qfalse, 0, 0 );
1614
1615 if ( tr.startsolid || tr.allsolid )
1616 {
1617 return;
1618 }
1619
1620 if ( tr.fraction < 1.0f )
1621 {
1622 VectorCopy( tr.endpos, start );
1623 }
1624 }
1625
WP_ExplosiveDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)1626 void WP_ExplosiveDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
1627 {
1628 laserTrapExplode(self);
1629 }
1630
1631 //----------------------------------------------
WP_flechette_alt_blow(gentity_t * ent)1632 void WP_flechette_alt_blow( gentity_t *ent )
1633 //----------------------------------------------
1634 {
1635 ent->s.pos.trDelta[0] = 1;
1636 ent->s.pos.trDelta[1] = 0;
1637 ent->s.pos.trDelta[2] = 0;
1638
1639 laserTrapExplode(ent);
1640 }
1641
1642 //------------------------------------------------------------------------------
WP_CreateFlechetteBouncyThing(vec3_t start,vec3_t fwd,gentity_t * self)1643 static void WP_CreateFlechetteBouncyThing( vec3_t start, vec3_t fwd, gentity_t *self )
1644 //------------------------------------------------------------------------------
1645 {
1646 gentity_t *missile = CreateMissile( start, fwd, 700 + Q_flrand(0.0f, 1.0f) * 700, 1500 + Q_flrand(0.0f, 1.0f) * 2000, self, qtrue );
1647
1648 missile->think = WP_flechette_alt_blow;
1649
1650 missile->activator = self;
1651
1652 missile->s.weapon = WP_FLECHETTE;
1653 missile->classname = "flech_alt";
1654 missile->mass = 4;
1655
1656 // How 'bout we give this thing a size...
1657 VectorSet( missile->r.mins, -3.0f, -3.0f, -3.0f );
1658 VectorSet( missile->r.maxs, 3.0f, 3.0f, 3.0f );
1659 missile->clipmask = MASK_SHOT;
1660
1661 missile->touch = touch_NULL;
1662
1663 // normal ones bounce, alt ones explode on impact
1664 missile->s.pos.trType = TR_GRAVITY;
1665
1666 missile->flags |= FL_BOUNCE_HALF;
1667 missile->s.eFlags |= EF_ALT_FIRING;
1668
1669 missile->bounceCount = 50;
1670
1671 missile->damage = FLECHETTE_ALT_DAMAGE;
1672 missile->dflags = 0;
1673 missile->splashDamage = FLECHETTE_ALT_SPLASH_DAM;
1674 missile->splashRadius = FLECHETTE_ALT_SPLASH_RAD;
1675
1676 missile->r.svFlags = SVF_USE_CURRENT_ORIGIN;
1677
1678 missile->methodOfDeath = MOD_FLECHETTE_ALT_SPLASH;
1679 missile->splashMethodOfDeath = MOD_FLECHETTE_ALT_SPLASH;
1680
1681 VectorCopy( start, missile->pos2 );
1682 }
1683
1684 //---------------------------------------------------------
WP_FlechetteAltFire(gentity_t * self)1685 static void WP_FlechetteAltFire( gentity_t *self )
1686 //---------------------------------------------------------
1687 {
1688 vec3_t dir, fwd, start, angs;
1689 int i;
1690
1691 vectoangles( forward, angs );
1692 VectorCopy( muzzle, start );
1693
1694 WP_TraceSetStart( self, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall
1695
1696 for ( i = 0; i < 2; i++ )
1697 {
1698 VectorCopy( angs, dir );
1699
1700 dir[PITCH] -= Q_flrand(0.0f, 1.0f) * 4 + 8; // make it fly upwards
1701 dir[YAW] += Q_flrand(-1.0f, 1.0f) * 2;
1702 AngleVectors( dir, fwd, NULL, NULL );
1703
1704 WP_CreateFlechetteBouncyThing( start, fwd, self );
1705 }
1706 }
1707
1708 //---------------------------------------------------------
WP_FireFlechette(gentity_t * ent,qboolean altFire)1709 static void WP_FireFlechette( gentity_t *ent, qboolean altFire )
1710 //---------------------------------------------------------
1711 {
1712 if ( altFire )
1713 {
1714 //WP_FlechetteProxMine( ent );
1715 WP_FlechetteAltFire(ent);
1716 }
1717 else
1718 {
1719 WP_FlechetteMainFire( ent );
1720 }
1721 }
1722
1723
1724
1725 /*
1726 ======================================================================
1727
1728 ROCKET LAUNCHER
1729
1730 ======================================================================
1731 */
1732
1733 //---------------------------------------------------------
rocketThink(gentity_t * ent)1734 void rocketThink( gentity_t *ent )
1735 //---------------------------------------------------------
1736 {
1737 vec3_t newdir, targetdir,
1738 rup={0,0,1}, right;
1739 vec3_t org;
1740 float dot, dot2, dis;
1741 int i;
1742 float vel = (ent->spawnflags&1)?ent->speed:ROCKET_VELOCITY;
1743
1744 if ( ent->genericValue1 && ent->genericValue1 < level.time )
1745 {//time's up, we're done, remove us
1746 if ( ent->genericValue2 )
1747 {//explode when die
1748 RocketDie( ent, &g_entities[ent->r.ownerNum], &g_entities[ent->r.ownerNum], 0, MOD_UNKNOWN );
1749 }
1750 else
1751 {//just remove when die
1752 G_FreeEntity( ent );
1753 }
1754 return;
1755 }
1756 if ( !ent->enemy
1757 || !ent->enemy->client
1758 || ent->enemy->health <= 0
1759 || ent->enemy->client->ps.powerups[PW_CLOAKED] )
1760 {//no enemy or enemy not a client or enemy dead or enemy cloaked
1761 if ( !ent->genericValue1 )
1762 {//doesn't have its own self-kill time
1763 ent->nextthink = level.time + 10000;
1764 ent->think = G_FreeEntity;
1765 }
1766 return;
1767 }
1768
1769 if ( (ent->spawnflags&1) )
1770 {//vehicle rocket
1771 if ( ent->enemy->client && ent->enemy->client->NPC_class == CLASS_VEHICLE )
1772 {//tracking another vehicle
1773 if ( ent->enemy->client->ps.speed+4000 > vel )
1774 {
1775 vel = ent->enemy->client->ps.speed+4000;
1776 }
1777 }
1778 }
1779
1780 if ( ent->enemy && ent->enemy->inuse )
1781 {
1782 float newDirMult = ent->angle?ent->angle*2.0f:1.0f;
1783 float oldDirMult = ent->angle?(1.0f-ent->angle)*2.0f:1.0f;
1784
1785 VectorCopy( ent->enemy->r.currentOrigin, org );
1786 org[2] += (ent->enemy->r.mins[2] + ent->enemy->r.maxs[2]) * 0.5f;
1787
1788 VectorSubtract( org, ent->r.currentOrigin, targetdir );
1789 VectorNormalize( targetdir );
1790
1791 // Now the rocket can't do a 180 in space, so we'll limit the turn to about 45 degrees.
1792 dot = DotProduct( targetdir, ent->movedir );
1793 if ( (ent->spawnflags&1) )
1794 {//vehicle rocket
1795 if ( ent->radius > -1.0f )
1796 {//can lose the lock if DotProduct drops below this number
1797 if ( dot < ent->radius )
1798 {//lost the lock!!!
1799 //HMM... maybe can re-lock on if they come in front again?
1800 /*
1801 //OR: should it stop trying to lock altogether?
1802 if ( ent->genericValue1 )
1803 {//have a timelimit, set next think to that
1804 ent->nextthink = ent->genericValue1;
1805 if ( ent->genericValue2 )
1806 {//explode when die
1807 ent->think = G_ExplodeMissile;
1808 }
1809 else
1810 {
1811 ent->think = G_FreeEntity;
1812 }
1813 }
1814 else
1815 {
1816 ent->think = NULL;
1817 ent->nextthink = -1;
1818 }
1819 */
1820 return;
1821 }
1822 }
1823 }
1824
1825
1826 // a dot of 1.0 means right-on-target.
1827 if ( dot < 0.0f )
1828 {
1829 // Go in the direction opposite, start a 180.
1830 CrossProduct( ent->movedir, rup, right );
1831 dot2 = DotProduct( targetdir, right );
1832
1833 if ( dot2 > 0 )
1834 {
1835 // Turn 45 degrees right.
1836 VectorMA( ent->movedir, 0.4f*newDirMult, right, newdir );
1837 }
1838 else
1839 {
1840 // Turn 45 degrees left.
1841 VectorMA( ent->movedir, -0.4f*newDirMult, right, newdir );
1842 }
1843
1844 // Yeah we've adjusted horizontally, but let's split the difference vertically, so we kinda try to move towards it.
1845 newdir[2] = ( (targetdir[2]*newDirMult) + (ent->movedir[2]*oldDirMult) ) * 0.5;
1846
1847 // let's also slow down a lot
1848 vel *= 0.5f;
1849 }
1850 else if ( dot < 0.70f )
1851 {
1852 // Still a bit off, so we turn a bit softer
1853 VectorMA( ent->movedir, 0.5f*newDirMult, targetdir, newdir );
1854 }
1855 else
1856 {
1857 // getting close, so turn a bit harder
1858 VectorMA( ent->movedir, 0.9f*newDirMult, targetdir, newdir );
1859 }
1860
1861 // add crazy drunkenness
1862 for (i = 0; i < 3; i++ )
1863 {
1864 newdir[i] += Q_flrand(-1.0f, 1.0f) * ent->random * 0.25f;
1865 }
1866
1867 // decay the randomness
1868 ent->random *= 0.9f;
1869
1870 if ( ent->enemy->client
1871 && ent->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
1872 {//tracking a client who's on the ground, aim at the floor...?
1873 // Try to crash into the ground if we get close enough to do splash damage
1874 dis = Distance( ent->r.currentOrigin, org );
1875
1876 if ( dis < 128 )
1877 {
1878 // the closer we get, the more we push the rocket down, heh heh.
1879 newdir[2] -= (1.0f - (dis / 128.0f)) * 0.6f;
1880 }
1881 }
1882
1883 VectorNormalize( newdir );
1884
1885 VectorScale( newdir, vel * 0.5f, ent->s.pos.trDelta );
1886 VectorCopy( newdir, ent->movedir );
1887 SnapVector( ent->s.pos.trDelta ); // save net bandwidth
1888 VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
1889 ent->s.pos.trTime = level.time;
1890 }
1891
1892 ent->nextthink = level.time + ROCKET_ALT_THINK_TIME; // Nothing at all spectacular happened, continue.
1893 return;
1894 }
1895
1896 extern void G_ExplodeMissile( gentity_t *ent );
RocketDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)1897 void RocketDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
1898 {
1899 self->die = 0;
1900 self->r.contents = 0;
1901
1902 G_ExplodeMissile( self );
1903
1904 self->think = G_FreeEntity;
1905 self->nextthink = level.time;
1906 }
1907
1908 //---------------------------------------------------------
WP_FireRocket(gentity_t * ent,qboolean altFire)1909 static void WP_FireRocket( gentity_t *ent, qboolean altFire )
1910 //---------------------------------------------------------
1911 {
1912 int damage = ROCKET_DAMAGE;
1913 int vel = ROCKET_VELOCITY;
1914 int dif = 0;
1915 float rTime;
1916 gentity_t *missile;
1917
1918 if ( altFire )
1919 {
1920 vel *= 0.5f;
1921 }
1922
1923 missile = CreateMissile( muzzle, forward, vel, 30000, ent, altFire );
1924
1925 if (ent->client && ent->client->ps.rocketLockIndex != ENTITYNUM_NONE)
1926 {
1927 float lockTimeInterval = ((level.gametype==GT_SIEGE)?2400.0f:1200.0f)/16.0f;
1928 rTime = ent->client->ps.rocketLockTime;
1929
1930 if (rTime == -1)
1931 {
1932 rTime = ent->client->ps.rocketLastValidTime;
1933 }
1934 dif = ( level.time - rTime ) / lockTimeInterval;
1935
1936 if (dif < 0)
1937 {
1938 dif = 0;
1939 }
1940
1941 //It's 10 even though it locks client-side at 8, because we want them to have a sturdy lock first, and because there's a slight difference in time between server and client
1942 if ( dif >= 10 && rTime != -1 )
1943 {
1944 missile->enemy = &g_entities[ent->client->ps.rocketLockIndex];
1945
1946 if (missile->enemy && missile->enemy->client && missile->enemy->health > 0 && !OnSameTeam(ent, missile->enemy))
1947 { //if enemy became invalid, died, or is on the same team, then don't seek it
1948 missile->angle = 0.5f;
1949 missile->think = rocketThink;
1950 missile->nextthink = level.time + ROCKET_ALT_THINK_TIME;
1951 }
1952 }
1953
1954 ent->client->ps.rocketLockIndex = ENTITYNUM_NONE;
1955 ent->client->ps.rocketLockTime = 0;
1956 ent->client->ps.rocketTargetTime = 0;
1957 }
1958
1959 missile->classname = "rocket_proj";
1960 missile->s.weapon = WP_ROCKET_LAUNCHER;
1961
1962 // Make it easier to hit things
1963 VectorSet( missile->r.maxs, ROCKET_SIZE, ROCKET_SIZE, ROCKET_SIZE );
1964 VectorScale( missile->r.maxs, -1, missile->r.mins );
1965
1966 missile->damage = damage;
1967 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1968 if (altFire)
1969 {
1970 missile->methodOfDeath = MOD_ROCKET_HOMING;
1971 missile->splashMethodOfDeath = MOD_ROCKET_HOMING_SPLASH;
1972 }
1973 else
1974 {
1975 missile->methodOfDeath = MOD_ROCKET;
1976 missile->splashMethodOfDeath = MOD_ROCKET_SPLASH;
1977 }
1978 //===testing being able to shoot rockets out of the air==================================
1979 missile->health = 10;
1980 missile->takedamage = qtrue;
1981 missile->r.contents = MASK_SHOT;
1982 missile->die = RocketDie;
1983 //===testing being able to shoot rockets out of the air==================================
1984
1985 missile->clipmask = MASK_SHOT;
1986 missile->splashDamage = ROCKET_SPLASH_DAMAGE;
1987 missile->splashRadius = ROCKET_SPLASH_RADIUS;
1988
1989 // we don't want it to ever bounce
1990 missile->bounceCount = 0;
1991 }
1992
1993 /*
1994 ======================================================================
1995
1996 THERMAL DETONATOR
1997
1998 ======================================================================
1999 */
2000
2001 #define TD_DAMAGE 70 //only do 70 on a direct impact
2002 #define TD_SPLASH_RAD 128
2003 #define TD_SPLASH_DAM 90
2004 #define TD_VELOCITY 900
2005 #define TD_MIN_CHARGE 0.15f
2006 #define TD_TIME 3000//6000
2007 #define TD_ALT_TIME 3000
2008
2009 #define TD_ALT_DAMAGE 60//100
2010 #define TD_ALT_SPLASH_RAD 128
2011 #define TD_ALT_SPLASH_DAM 50//90
2012 #define TD_ALT_VELOCITY 600
2013 #define TD_ALT_MIN_CHARGE 0.15f
2014 #define TD_ALT_TIME 3000
2015
2016 void thermalThinkStandard(gentity_t *ent);
2017
2018 //---------------------------------------------------------
thermalDetonatorExplode(gentity_t * ent)2019 void thermalDetonatorExplode( gentity_t *ent )
2020 //---------------------------------------------------------
2021 {
2022 if ( !ent->count )
2023 {
2024 G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/thermal/warning.wav" ) );
2025 ent->count = 1;
2026 ent->genericValue5 = level.time + 500;
2027 ent->think = thermalThinkStandard;
2028 ent->nextthink = level.time;
2029 ent->r.svFlags |= SVF_BROADCAST;//so everyone hears/sees the explosion?
2030 }
2031 else
2032 {
2033 vec3_t origin;
2034 vec3_t dir={0,0,1};
2035
2036 BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
2037 origin[2] += 8;
2038 SnapVector( origin );
2039 G_SetOrigin( ent, origin );
2040
2041 ent->s.eType = ET_GENERAL;
2042 G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) );
2043 ent->freeAfterEvent = qtrue;
2044
2045 if (G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage, ent->splashRadius,
2046 ent, ent, ent->splashMethodOfDeath))
2047 {
2048 g_entities[ent->r.ownerNum].client->accuracy_hits++;
2049 }
2050
2051 trap->LinkEntity( (sharedEntity_t *)ent );
2052 }
2053 }
2054
thermalThinkStandard(gentity_t * ent)2055 void thermalThinkStandard(gentity_t *ent)
2056 {
2057 if (ent->genericValue5 < level.time)
2058 {
2059 ent->think = thermalDetonatorExplode;
2060 ent->nextthink = level.time;
2061 return;
2062 }
2063
2064 G_RunObject(ent);
2065 ent->nextthink = level.time;
2066 }
2067
2068 //---------------------------------------------------------
WP_FireThermalDetonator(gentity_t * ent,qboolean altFire)2069 gentity_t *WP_FireThermalDetonator( gentity_t *ent, qboolean altFire )
2070 //---------------------------------------------------------
2071 {
2072 gentity_t *bolt;
2073 vec3_t dir, start;
2074 float chargeAmount = 1.0f; // default of full charge
2075
2076 VectorCopy( forward, dir );
2077 VectorCopy( muzzle, start );
2078
2079 bolt = G_Spawn();
2080
2081 bolt->physicsObject = qtrue;
2082
2083 bolt->classname = "thermal_detonator";
2084 bolt->think = thermalThinkStandard;
2085 bolt->nextthink = level.time;
2086 bolt->touch = touch_NULL;
2087
2088 // How 'bout we give this thing a size...
2089 VectorSet( bolt->r.mins, -3.0f, -3.0f, -3.0f );
2090 VectorSet( bolt->r.maxs, 3.0f, 3.0f, 3.0f );
2091 bolt->clipmask = MASK_SHOT;
2092
2093 W_TraceSetStart( ent, start, bolt->r.mins, bolt->r.maxs );//make sure our start point isn't on the other side of a wall
2094
2095 if ( ent->client )
2096 {
2097 chargeAmount = level.time - ent->client->ps.weaponChargeTime;
2098 }
2099
2100 // get charge amount
2101 chargeAmount = chargeAmount / (float)TD_VELOCITY;
2102
2103 if ( chargeAmount > 1.0f )
2104 {
2105 chargeAmount = 1.0f;
2106 }
2107 else if ( chargeAmount < TD_MIN_CHARGE )
2108 {
2109 chargeAmount = TD_MIN_CHARGE;
2110 }
2111
2112 // normal ones bounce, alt ones explode on impact
2113 bolt->genericValue5 = level.time + TD_TIME; // How long 'til she blows
2114 bolt->s.pos.trType = TR_GRAVITY;
2115 bolt->parent = ent;
2116 bolt->r.ownerNum = ent->s.number;
2117 VectorScale( dir, TD_VELOCITY * chargeAmount, bolt->s.pos.trDelta );
2118
2119 if ( ent->health >= 0 )
2120 {
2121 bolt->s.pos.trDelta[2] += 120;
2122 }
2123
2124 if ( !altFire )
2125 {
2126 bolt->flags |= FL_BOUNCE_HALF;
2127 }
2128
2129 bolt->s.loopSound = G_SoundIndex( "sound/weapons/thermal/thermloop.wav" );
2130 bolt->s.loopIsSoundset = qfalse;
2131
2132 bolt->damage = TD_DAMAGE;
2133 bolt->dflags = 0;
2134 bolt->splashDamage = TD_SPLASH_DAM;
2135 bolt->splashRadius = TD_SPLASH_RAD;
2136
2137 bolt->s.eType = ET_MISSILE;
2138 bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
2139 bolt->s.weapon = WP_THERMAL;
2140
2141 bolt->methodOfDeath = MOD_THERMAL;
2142 bolt->splashMethodOfDeath = MOD_THERMAL_SPLASH;
2143
2144 bolt->s.pos.trTime = level.time; // move a bit on the very first frame
2145 VectorCopy( start, bolt->s.pos.trBase );
2146
2147 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
2148 VectorCopy (start, bolt->r.currentOrigin);
2149
2150 VectorCopy( start, bolt->pos2 );
2151
2152 bolt->bounceCount = -5;
2153
2154 return bolt;
2155 }
2156
WP_DropThermal(gentity_t * ent)2157 gentity_t *WP_DropThermal( gentity_t *ent )
2158 {
2159 AngleVectors( ent->client->ps.viewangles, forward, vright, up );
2160 return (WP_FireThermalDetonator( ent, qfalse ));
2161 }
2162
2163
2164 //---------------------------------------------------------
WP_LobFire(gentity_t * self,vec3_t start,vec3_t target,vec3_t mins,vec3_t maxs,int clipmask,vec3_t velocity,qboolean tracePath,int ignoreEntNum,int enemyNum,float minSpeed,float maxSpeed,float idealSpeed,qboolean mustHit)2165 qboolean WP_LobFire( gentity_t *self, vec3_t start, vec3_t target, vec3_t mins, vec3_t maxs, int clipmask,
2166 vec3_t velocity, qboolean tracePath, int ignoreEntNum, int enemyNum,
2167 float minSpeed, float maxSpeed, float idealSpeed, qboolean mustHit )
2168 //---------------------------------------------------------
2169 { //for the galak mech NPC
2170 float targetDist, shotSpeed, speedInc = 100, travelTime, impactDist, bestImpactDist = Q3_INFINITE;//fireSpeed,
2171 vec3_t targetDir, shotVel, failCase;
2172 trace_t trace;
2173 trajectory_t tr;
2174 qboolean blocked;
2175 int elapsedTime, skipNum, timeStep = 500, hitCount = 0, maxHits = 7;
2176 vec3_t lastPos, testPos;
2177 gentity_t *traceEnt;
2178
2179 if ( !idealSpeed )
2180 {
2181 idealSpeed = 300;
2182 }
2183 else if ( idealSpeed < speedInc )
2184 {
2185 idealSpeed = speedInc;
2186 }
2187 shotSpeed = idealSpeed;
2188 skipNum = (idealSpeed-speedInc)/speedInc;
2189 if ( !minSpeed )
2190 {
2191 minSpeed = 100;
2192 }
2193 if ( !maxSpeed )
2194 {
2195 maxSpeed = 900;
2196 }
2197 while ( hitCount < maxHits )
2198 {
2199 VectorSubtract( target, start, targetDir );
2200 targetDist = VectorNormalize( targetDir );
2201
2202 VectorScale( targetDir, shotSpeed, shotVel );
2203 travelTime = targetDist/shotSpeed;
2204 shotVel[2] += travelTime * 0.5 * g_gravity.value;
2205
2206 if ( !hitCount )
2207 {//save the first (ideal) one as the failCase (fallback value)
2208 if ( !mustHit )
2209 {//default is fine as a return value
2210 VectorCopy( shotVel, failCase );
2211 }
2212 }
2213
2214 if ( tracePath )
2215 {//do a rough trace of the path
2216 blocked = qfalse;
2217
2218 VectorCopy( start, tr.trBase );
2219 VectorCopy( shotVel, tr.trDelta );
2220 tr.trType = TR_GRAVITY;
2221 tr.trTime = level.time;
2222 travelTime *= 1000.0f;
2223 VectorCopy( start, lastPos );
2224
2225 //This may be kind of wasteful, especially on long throws... use larger steps? Divide the travelTime into a certain hard number of slices? Trace just to apex and down?
2226 for ( elapsedTime = timeStep; elapsedTime < floor(travelTime)+timeStep; elapsedTime += timeStep )
2227 {
2228 if ( (float)elapsedTime > travelTime )
2229 {//cap it
2230 elapsedTime = floor( travelTime );
2231 }
2232 BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos );
2233 trap->Trace( &trace, lastPos, mins, maxs, testPos, ignoreEntNum, clipmask, qfalse, 0, 0 );
2234
2235 if ( trace.allsolid || trace.startsolid )
2236 {
2237 blocked = qtrue;
2238 break;
2239 }
2240 if ( trace.fraction < 1.0f )
2241 {//hit something
2242 if ( trace.entityNum == enemyNum )
2243 {//hit the enemy, that's perfect!
2244 break;
2245 }
2246 else if ( trace.plane.normal[2] > 0.7 && DistanceSquared( trace.endpos, target ) < 4096 )//hit within 64 of desired location, should be okay
2247 {//close enough!
2248 break;
2249 }
2250 else
2251 {//FIXME: maybe find the extents of this brush and go above or below it on next try somehow?
2252 impactDist = DistanceSquared( trace.endpos, target );
2253 if ( impactDist < bestImpactDist )
2254 {
2255 bestImpactDist = impactDist;
2256 VectorCopy( shotVel, failCase );
2257 }
2258 blocked = qtrue;
2259 //see if we should store this as the failCase
2260 if ( trace.entityNum < ENTITYNUM_WORLD )
2261 {//hit an ent
2262 traceEnt = &g_entities[trace.entityNum];
2263 if ( traceEnt && traceEnt->takedamage && !OnSameTeam( self, traceEnt ) )
2264 {//hit something breakable, so that's okay
2265 //we haven't found a clear shot yet so use this as the failcase
2266 VectorCopy( shotVel, failCase );
2267 }
2268 }
2269 break;
2270 }
2271 }
2272 if ( elapsedTime == floor( travelTime ) )
2273 {//reached end, all clear
2274 break;
2275 }
2276 else
2277 {
2278 //all clear, try next slice
2279 VectorCopy( testPos, lastPos );
2280 }
2281 }
2282 if ( blocked )
2283 {//hit something, adjust speed (which will change arc)
2284 hitCount++;
2285 shotSpeed = idealSpeed + ((hitCount-skipNum) * speedInc);//from min to max (skipping ideal)
2286 if ( hitCount >= skipNum )
2287 {//skip ideal since that was the first value we tested
2288 shotSpeed += speedInc;
2289 }
2290 }
2291 else
2292 {//made it!
2293 break;
2294 }
2295 }
2296 else
2297 {//no need to check the path, go with first calc
2298 break;
2299 }
2300 }
2301
2302 if ( hitCount >= maxHits )
2303 {//NOTE: worst case scenario, use the one that impacted closest to the target (or just use the first try...?)
2304 VectorCopy( failCase, velocity );
2305 return qfalse;
2306 }
2307 VectorCopy( shotVel, velocity );
2308 return qtrue;
2309 }
2310
2311 /*
2312 ======================================================================
2313
2314 LASER TRAP / TRIP MINE
2315
2316 ======================================================================
2317 */
2318 #define LT_DAMAGE 100
2319 #define LT_SPLASH_RAD 256.0f
2320 #define LT_SPLASH_DAM 105
2321 #define LT_VELOCITY 900.0f
2322 #define LT_SIZE 1.5f
2323 #define LT_ALT_TIME 2000
2324 #define LT_ACTIVATION_DELAY 1000
2325 #define LT_DELAY_TIME 50
2326
laserTrapExplode(gentity_t * self)2327 void laserTrapExplode( gentity_t *self )
2328 {
2329 vec3_t v;
2330 self->takedamage = qfalse;
2331
2332 if (self->activator)
2333 {
2334 G_RadiusDamage( self->r.currentOrigin, self->activator, self->splashDamage, self->splashRadius, self, self, MOD_TRIP_MINE_SPLASH/*MOD_LT_SPLASH*/ );
2335 }
2336
2337 if (self->s.weapon != WP_FLECHETTE)
2338 {
2339 G_AddEvent( self, EV_MISSILE_MISS, 0);
2340 }
2341
2342 VectorCopy(self->s.pos.trDelta, v);
2343 //Explode outward from the surface
2344
2345 if (self->s.time == -2)
2346 {
2347 v[0] = 0;
2348 v[1] = 0;
2349 v[2] = 0;
2350 }
2351
2352 if (self->s.weapon == WP_FLECHETTE)
2353 {
2354 G_PlayEffect(EFFECT_EXPLOSION_FLECHETTE, self->r.currentOrigin, v);
2355 }
2356 else
2357 {
2358 G_PlayEffect(EFFECT_EXPLOSION_TRIPMINE, self->r.currentOrigin, v);
2359 }
2360
2361 self->think = G_FreeEntity;
2362 self->nextthink = level.time;
2363 }
2364
laserTrapDelayedExplode(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)2365 void laserTrapDelayedExplode( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
2366 {
2367 self->enemy = attacker;
2368 self->think = laserTrapExplode;
2369 self->nextthink = level.time + FRAMETIME;
2370 self->takedamage = qfalse;
2371 if ( attacker && attacker->s.number < MAX_CLIENTS )
2372 {
2373 //less damage when shot by player
2374 self->splashDamage /= 3;
2375 self->splashRadius /= 3;
2376 }
2377 }
2378
touchLaserTrap(gentity_t * ent,gentity_t * other,trace_t * trace)2379 void touchLaserTrap( gentity_t *ent, gentity_t *other, trace_t *trace )
2380 {
2381 if (other && other->s.number < ENTITYNUM_WORLD)
2382 { //just explode if we hit any entity. This way we don't have things happening like tripmines floating
2383 //in the air after getting stuck to a moving door
2384 if ( ent->activator != other )
2385 {
2386 ent->touch = 0;
2387 ent->nextthink = level.time + FRAMETIME;
2388 ent->think = laserTrapExplode;
2389 VectorCopy(trace->plane.normal, ent->s.pos.trDelta);
2390 }
2391 }
2392 else
2393 {
2394 ent->touch = 0;
2395 if (trace->entityNum != ENTITYNUM_NONE)
2396 {
2397 ent->enemy = &g_entities[trace->entityNum];
2398 }
2399 laserTrapStick(ent, trace->endpos, trace->plane.normal);
2400 }
2401 }
2402
proxMineThink(gentity_t * ent)2403 void proxMineThink(gentity_t *ent)
2404 {
2405 int i = 0;
2406 gentity_t *cl;
2407 gentity_t *owner = NULL;
2408
2409 if (ent->r.ownerNum < ENTITYNUM_WORLD)
2410 {
2411 owner = &g_entities[ent->r.ownerNum];
2412 }
2413
2414 ent->nextthink = level.time;
2415
2416 if (ent->genericValue15 < level.time ||
2417 !owner ||
2418 !owner->inuse ||
2419 !owner->client ||
2420 owner->client->pers.connected != CON_CONNECTED)
2421 { //time to die!
2422 ent->think = laserTrapExplode;
2423 return;
2424 }
2425
2426 while (i < MAX_CLIENTS)
2427 { //eh, just check for clients, don't care about anyone else...
2428 cl = &g_entities[i];
2429
2430 if (cl->inuse && cl->client && cl->client->pers.connected == CON_CONNECTED &&
2431 owner != cl && cl->client->sess.sessionTeam != TEAM_SPECTATOR &&
2432 cl->client->tempSpectate < level.time && cl->health > 0)
2433 {
2434 if (!OnSameTeam(owner, cl) || g_friendlyFire.integer)
2435 { //not on the same team, or friendly fire is enabled
2436 vec3_t v;
2437
2438 VectorSubtract(ent->r.currentOrigin, cl->client->ps.origin, v);
2439 if (VectorLength(v) < (ent->splashRadius/2.0f))
2440 {
2441 ent->think = laserTrapExplode;
2442 return;
2443 }
2444 }
2445 }
2446 i++;
2447 }
2448 }
2449
laserTrapThink(gentity_t * ent)2450 void laserTrapThink ( gentity_t *ent )
2451 {
2452 gentity_t *traceEnt;
2453 vec3_t end;
2454 trace_t tr;
2455
2456 //just relink it every think
2457 trap->LinkEntity((sharedEntity_t *)ent);
2458
2459 //turn on the beam effect
2460 if ( !(ent->s.eFlags&EF_FIRING) )
2461 {//arm me
2462 G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/laser_trap/warning.wav" ) );
2463 ent->s.eFlags |= EF_FIRING;
2464 }
2465 ent->think = laserTrapThink;
2466 ent->nextthink = level.time + FRAMETIME;
2467
2468 // Find the main impact point
2469 VectorMA ( ent->s.pos.trBase, 1024, ent->movedir, end );
2470 trap->Trace ( &tr, ent->r.currentOrigin, NULL, NULL, end, ent->s.number, MASK_SHOT, qfalse, 0, 0);
2471
2472 traceEnt = &g_entities[ tr.entityNum ];
2473
2474 ent->s.time = -1; //let all clients know to draw a beam from this guy
2475
2476 if ( traceEnt->client || tr.startsolid )
2477 {
2478 //go boom
2479 ent->touch = 0;
2480 ent->nextthink = level.time + LT_DELAY_TIME;
2481 ent->think = laserTrapExplode;
2482 }
2483 }
2484
laserTrapStick(gentity_t * ent,vec3_t endpos,vec3_t normal)2485 void laserTrapStick( gentity_t *ent, vec3_t endpos, vec3_t normal )
2486 {
2487 G_SetOrigin( ent, endpos );
2488 VectorCopy( normal, ent->pos1 );
2489
2490 VectorClear( ent->s.apos.trDelta );
2491 // This will orient the object to face in the direction of the normal
2492 VectorCopy( normal, ent->s.pos.trDelta );
2493 //VectorScale( normal, -1, ent->s.pos.trDelta );
2494 ent->s.pos.trTime = level.time;
2495
2496
2497 //This does nothing, cg_missile makes assumptions about direction of travel controlling angles
2498 vectoangles( normal, ent->s.apos.trBase );
2499 VectorClear( ent->s.apos.trDelta );
2500 ent->s.apos.trType = TR_STATIONARY;
2501 VectorCopy( ent->s.apos.trBase, ent->s.angles );
2502 VectorCopy( ent->s.angles, ent->r.currentAngles );
2503
2504
2505 G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/laser_trap/stick.wav" ) );
2506 if ( ent->count )
2507 {//a tripwire
2508 //add draw line flag
2509 VectorCopy( normal, ent->movedir );
2510 ent->think = laserTrapThink;
2511 ent->nextthink = level.time + LT_ACTIVATION_DELAY;//delay the activation
2512 ent->touch = touch_NULL;
2513 //make it shootable
2514 ent->takedamage = qtrue;
2515 ent->health = 5;
2516 ent->die = laserTrapDelayedExplode;
2517
2518 //shove the box through the wall
2519 VectorSet( ent->r.mins, -LT_SIZE*2, -LT_SIZE*2, -LT_SIZE*2 );
2520 VectorSet( ent->r.maxs, LT_SIZE*2, LT_SIZE*2, LT_SIZE*2 );
2521
2522 //so that the owner can blow it up with projectiles
2523 ent->r.svFlags |= SVF_OWNERNOTSHARED;
2524 }
2525 else
2526 {
2527 ent->touch = touchLaserTrap;
2528 ent->think = proxMineThink;//laserTrapExplode;
2529 ent->genericValue15 = level.time + 30000; //auto-explode after 30 seconds.
2530 ent->nextthink = level.time + LT_ALT_TIME; // How long 'til she blows
2531
2532 //make it shootable
2533 ent->takedamage = qtrue;
2534 ent->health = 5;
2535 ent->die = laserTrapDelayedExplode;
2536
2537 //shove the box through the wall
2538 VectorSet( ent->r.mins, -LT_SIZE*2, -LT_SIZE*2, -LT_SIZE*2 );
2539 VectorSet( ent->r.maxs, LT_SIZE*2, LT_SIZE*2, LT_SIZE*2 );
2540
2541 //so that the owner can blow it up with projectiles
2542 ent->r.svFlags |= SVF_OWNERNOTSHARED;
2543
2544 if ( !(ent->s.eFlags&EF_FIRING) )
2545 {//arm me
2546 G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/laser_trap/warning.wav" ) );
2547 ent->s.eFlags |= EF_FIRING;
2548 ent->s.time = -1;
2549 ent->s.bolt2 = 1;
2550 }
2551 }
2552 }
2553
TrapThink(gentity_t * ent)2554 void TrapThink(gentity_t *ent)
2555 { //laser trap think
2556 ent->nextthink = level.time + 50;
2557 G_RunObject(ent);
2558 }
2559
CreateLaserTrap(gentity_t * laserTrap,vec3_t start,gentity_t * owner)2560 void CreateLaserTrap( gentity_t *laserTrap, vec3_t start, gentity_t *owner )
2561 { //create a laser trap entity
2562 laserTrap->classname = "laserTrap";
2563 laserTrap->flags |= FL_BOUNCE_HALF;
2564 laserTrap->s.eFlags |= EF_MISSILE_STICK;
2565 laserTrap->splashDamage = LT_SPLASH_DAM;
2566 laserTrap->splashRadius = LT_SPLASH_RAD;
2567 laserTrap->damage = LT_DAMAGE;
2568 laserTrap->methodOfDeath = MOD_TRIP_MINE_SPLASH;
2569 laserTrap->splashMethodOfDeath = MOD_TRIP_MINE_SPLASH;
2570 laserTrap->s.eType = ET_GENERAL;
2571 laserTrap->r.svFlags = SVF_USE_CURRENT_ORIGIN;
2572 laserTrap->s.weapon = WP_TRIP_MINE;
2573 laserTrap->s.pos.trType = TR_GRAVITY;
2574 laserTrap->r.contents = MASK_SHOT;
2575 laserTrap->parent = owner;
2576 laserTrap->activator = owner;
2577 laserTrap->r.ownerNum = owner->s.number;
2578 VectorSet( laserTrap->r.mins, -LT_SIZE, -LT_SIZE, -LT_SIZE );
2579 VectorSet( laserTrap->r.maxs, LT_SIZE, LT_SIZE, LT_SIZE );
2580 laserTrap->clipmask = MASK_SHOT;
2581 laserTrap->s.solid = 2;
2582 laserTrap->s.modelindex = G_ModelIndex( "models/weapons2/laser_trap/laser_trap_w.glm" );
2583 laserTrap->s.modelGhoul2 = 1;
2584 laserTrap->s.g2radius = 40;
2585
2586 laserTrap->s.genericenemyindex = owner->s.number+MAX_GENTITIES;
2587
2588 laserTrap->health = 1;
2589
2590 laserTrap->s.time = 0;
2591
2592 laserTrap->s.pos.trTime = level.time; // move a bit on the very first frame
2593 VectorCopy( start, laserTrap->s.pos.trBase );
2594 SnapVector( laserTrap->s.pos.trBase ); // save net bandwidth
2595
2596 SnapVector( laserTrap->s.pos.trDelta ); // save net bandwidth
2597 VectorCopy (start, laserTrap->r.currentOrigin);
2598
2599 laserTrap->s.apos.trType = TR_GRAVITY;
2600 laserTrap->s.apos.trTime = level.time;
2601 laserTrap->s.apos.trBase[YAW] = rand()%360;
2602 laserTrap->s.apos.trBase[PITCH] = rand()%360;
2603 laserTrap->s.apos.trBase[ROLL] = rand()%360;
2604
2605 if (rand()%10 < 5)
2606 {
2607 laserTrap->s.apos.trBase[YAW] = -laserTrap->s.apos.trBase[YAW];
2608 }
2609
2610 VectorCopy( start, laserTrap->pos2 );
2611 laserTrap->touch = touchLaserTrap;
2612 laserTrap->think = TrapThink;
2613 laserTrap->nextthink = level.time + 50;
2614 }
2615
WP_PlaceLaserTrap(gentity_t * ent,qboolean alt_fire)2616 void WP_PlaceLaserTrap( gentity_t *ent, qboolean alt_fire )
2617 {
2618 gentity_t *laserTrap;
2619 gentity_t *found = NULL;
2620 vec3_t dir, start;
2621 int trapcount = 0;
2622 int foundLaserTraps[MAX_GENTITIES];
2623 int trapcount_org;
2624 int lowestTimeStamp;
2625 int removeMe;
2626 int i;
2627
2628 foundLaserTraps[0] = ENTITYNUM_NONE;
2629
2630 VectorCopy( forward, dir );
2631 VectorCopy( muzzle, start );
2632
2633 laserTrap = G_Spawn();
2634
2635 //limit to 10 placed at any one time
2636 //see how many there are now
2637 while ( (found = G_Find( found, FOFS(classname), "laserTrap" )) != NULL )
2638 {
2639 if ( found->parent != ent )
2640 {
2641 continue;
2642 }
2643 foundLaserTraps[trapcount++] = found->s.number;
2644 }
2645 //now remove first ones we find until there are only 9 left
2646 found = NULL;
2647 trapcount_org = trapcount;
2648 lowestTimeStamp = level.time;
2649 while ( trapcount > 9 )
2650 {
2651 removeMe = -1;
2652 for ( i = 0; i < trapcount_org; i++ )
2653 {
2654 if ( foundLaserTraps[i] == ENTITYNUM_NONE )
2655 {
2656 continue;
2657 }
2658 found = &g_entities[foundLaserTraps[i]];
2659 if ( laserTrap && found->setTime < lowestTimeStamp )
2660 {
2661 removeMe = i;
2662 lowestTimeStamp = found->setTime;
2663 }
2664 }
2665 if ( removeMe != -1 )
2666 {
2667 //remove it... or blow it?
2668 if ( &g_entities[foundLaserTraps[removeMe]] == NULL )
2669 {
2670 break;
2671 }
2672 else
2673 {
2674 G_FreeEntity( &g_entities[foundLaserTraps[removeMe]] );
2675 }
2676 foundLaserTraps[removeMe] = ENTITYNUM_NONE;
2677 trapcount--;
2678 }
2679 else
2680 {
2681 break;
2682 }
2683 }
2684
2685 //now make the new one
2686 CreateLaserTrap( laserTrap, start, ent );
2687
2688 //set player-created-specific fields
2689 laserTrap->setTime = level.time;//remember when we placed it
2690
2691 if (!alt_fire)
2692 {//tripwire
2693 laserTrap->count = 1;
2694 }
2695
2696 //move it
2697 laserTrap->s.pos.trType = TR_GRAVITY;
2698
2699 if (alt_fire)
2700 {
2701 VectorScale( dir, 512, laserTrap->s.pos.trDelta );
2702 }
2703 else
2704 {
2705 VectorScale( dir, 256, laserTrap->s.pos.trDelta );
2706 }
2707
2708 trap->LinkEntity((sharedEntity_t *)laserTrap);
2709 }
2710
2711
2712 /*
2713 ======================================================================
2714
2715 DET PACK
2716
2717 ======================================================================
2718 */
VectorNPos(vec3_t in,vec3_t out)2719 void VectorNPos(vec3_t in, vec3_t out)
2720 {
2721 if (in[0] < 0) { out[0] = -in[0]; } else { out[0] = in[0]; }
2722 if (in[1] < 0) { out[1] = -in[1]; } else { out[1] = in[1]; }
2723 if (in[2] < 0) { out[2] = -in[2]; } else { out[2] = in[2]; }
2724 }
2725
2726 void DetPackBlow(gentity_t *self);
2727
charge_stick(gentity_t * self,gentity_t * other,trace_t * trace)2728 void charge_stick (gentity_t *self, gentity_t *other, trace_t *trace)
2729 {
2730 gentity_t *tent;
2731
2732 if ( other
2733 && (other->flags&FL_BBRUSH)
2734 && other->s.pos.trType == TR_STATIONARY
2735 && other->s.apos.trType == TR_STATIONARY )
2736 {//a perfectly still breakable brush, let us attach directly to it!
2737 self->target_ent = other;//remember them when we blow up
2738 }
2739 else if ( other
2740 && other->s.number < ENTITYNUM_WORLD
2741 && other->s.eType == ET_MOVER
2742 && trace->plane.normal[2] > 0 )
2743 {//stick to it?
2744 self->s.groundEntityNum = other->s.number;
2745 }
2746 else if (other && other->s.number < ENTITYNUM_WORLD &&
2747 (other->client || !other->s.weapon))
2748 { //hit another entity that is not stickable, "bounce" off
2749 vec3_t vNor, tN;
2750
2751 VectorCopy(trace->plane.normal, vNor);
2752 VectorNormalize(vNor);
2753 VectorNPos(self->s.pos.trDelta, tN);
2754 self->s.pos.trDelta[0] += vNor[0]*(tN[0]*(((float)Q_irand(1, 10))*0.1));
2755 self->s.pos.trDelta[1] += vNor[1]*(tN[1]*(((float)Q_irand(1, 10))*0.1));
2756 self->s.pos.trDelta[2] += vNor[2]*(tN[2]*(((float)Q_irand(1, 10))*0.1));
2757
2758 vectoangles(vNor, self->s.angles);
2759 vectoangles(vNor, self->s.apos.trBase);
2760 self->touch = charge_stick;
2761 return;
2762 }
2763 else if (other && other->s.number < ENTITYNUM_WORLD)
2764 { //hit an entity that we just want to explode on (probably another projectile or something)
2765 vec3_t v;
2766
2767 self->touch = 0;
2768 self->think = 0;
2769 self->nextthink = 0;
2770
2771 self->takedamage = qfalse;
2772
2773 VectorClear(self->s.apos.trDelta);
2774 self->s.apos.trType = TR_STATIONARY;
2775
2776 G_RadiusDamage( self->r.currentOrigin, self->parent, self->splashDamage, self->splashRadius, self, self, MOD_DET_PACK_SPLASH );
2777 VectorCopy(trace->plane.normal, v);
2778 VectorCopy(v, self->pos2);
2779 self->count = -1;
2780 G_PlayEffect(EFFECT_EXPLOSION_DETPACK, self->r.currentOrigin, v);
2781
2782 self->think = G_FreeEntity;
2783 self->nextthink = level.time;
2784 return;
2785 }
2786
2787 //if we get here I guess we hit hte world so we can stick to it
2788
2789 // This requires a bit of explaining.
2790 // When you suicide, all of the detpacks you have placed (either on a wall, or still falling in the air) will have
2791 // their ent->think() set to DetPackBlow and ent->nextthink will be between 100 <-> 300
2792 // If your detpacks land on a surface (i.e. charge_stick gets called) within that 100<->300 ms then ent->think()
2793 // will be overwritten (set to DetpackBlow) and ent->nextthink will be 30000
2794 // The end result is your detpacks won't explode, but will be stuck to the wall for 30 seconds without being able to
2795 // detonate them (or shoot them)
2796 // The fix Sil came up with is to check the think() function in charge_stick, and only overwrite it if they haven't
2797 // been primed to detonate
2798
2799 if ( self->think == G_RunObject ) {
2800 self->touch = 0;
2801 self->think = DetPackBlow;
2802 self->nextthink = level.time + 30000;
2803 }
2804
2805 VectorClear(self->s.apos.trDelta);
2806 self->s.apos.trType = TR_STATIONARY;
2807
2808 self->s.pos.trType = TR_STATIONARY;
2809 VectorCopy( self->r.currentOrigin, self->s.origin );
2810 VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
2811 VectorClear( self->s.pos.trDelta );
2812
2813 VectorClear( self->s.apos.trDelta );
2814
2815 VectorNormalize(trace->plane.normal);
2816
2817 vectoangles(trace->plane.normal, self->s.angles);
2818 VectorCopy(self->s.angles, self->r.currentAngles );
2819 VectorCopy(self->s.angles, self->s.apos.trBase);
2820
2821 VectorCopy(trace->plane.normal, self->pos2);
2822 self->count = -1;
2823
2824 G_Sound(self, CHAN_WEAPON, G_SoundIndex("sound/weapons/detpack/stick.wav"));
2825
2826 tent = G_TempEntity( self->r.currentOrigin, EV_MISSILE_MISS );
2827 tent->s.weapon = 0;
2828 tent->parent = self;
2829 tent->r.ownerNum = self->s.number;
2830
2831 //so that the owner can blow it up with projectiles
2832 self->r.svFlags |= SVF_OWNERNOTSHARED;
2833 }
2834
DetPackBlow(gentity_t * self)2835 void DetPackBlow(gentity_t *self)
2836 {
2837 vec3_t v;
2838
2839 self->pain = 0;
2840 self->die = 0;
2841 self->takedamage = qfalse;
2842
2843 if ( self->target_ent )
2844 {//we were attached to something, do *direct* damage to it!
2845 G_Damage( self->target_ent, self, &g_entities[self->r.ownerNum], v, self->r.currentOrigin, self->damage, 0, MOD_DET_PACK_SPLASH );
2846 }
2847 G_RadiusDamage( self->r.currentOrigin, self->parent, self->splashDamage, self->splashRadius, self, self, MOD_DET_PACK_SPLASH );
2848 v[0] = 0;
2849 v[1] = 0;
2850 v[2] = 1;
2851
2852 if (self->count == -1)
2853 {
2854 VectorCopy(self->pos2, v);
2855 }
2856
2857 G_PlayEffect(EFFECT_EXPLOSION_DETPACK, self->r.currentOrigin, v);
2858
2859 self->think = G_FreeEntity;
2860 self->nextthink = level.time;
2861 }
2862
DetPackPain(gentity_t * self,gentity_t * attacker,int damage)2863 void DetPackPain(gentity_t *self, gentity_t *attacker, int damage)
2864 {
2865 self->think = DetPackBlow;
2866 self->nextthink = level.time + Q_irand(50, 100);
2867 self->takedamage = qfalse;
2868 }
2869
DetPackDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)2870 void DetPackDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
2871 {
2872 self->think = DetPackBlow;
2873 self->nextthink = level.time + Q_irand(50, 100);
2874 self->takedamage = qfalse;
2875 }
2876
drop_charge(gentity_t * self,vec3_t start,vec3_t dir)2877 void drop_charge (gentity_t *self, vec3_t start, vec3_t dir)
2878 {
2879 gentity_t *bolt;
2880
2881 VectorNormalize (dir);
2882
2883 bolt = G_Spawn();
2884 bolt->classname = "detpack";
2885 bolt->nextthink = level.time + FRAMETIME;
2886 bolt->think = G_RunObject;
2887 bolt->s.eType = ET_GENERAL;
2888 bolt->s.g2radius = 100;
2889 bolt->s.modelGhoul2 = 1;
2890 bolt->s.modelindex = G_ModelIndex("models/weapons2/detpack/det_pack_proj.glm");
2891
2892 bolt->parent = self;
2893 bolt->r.ownerNum = self->s.number;
2894 bolt->damage = 100;
2895 bolt->splashDamage = 200;
2896 bolt->splashRadius = 200;
2897 bolt->methodOfDeath = MOD_DET_PACK_SPLASH;
2898 bolt->splashMethodOfDeath = MOD_DET_PACK_SPLASH;
2899 bolt->clipmask = MASK_SHOT;
2900 bolt->s.solid = 2;
2901 bolt->r.contents = MASK_SHOT;
2902 bolt->touch = charge_stick;
2903
2904 bolt->physicsObject = qtrue;
2905
2906 bolt->s.genericenemyindex = self->s.number+MAX_GENTITIES;
2907 //rww - so client prediction knows we own this and won't hit it
2908
2909 VectorSet( bolt->r.mins, -2, -2, -2 );
2910 VectorSet( bolt->r.maxs, 2, 2, 2 );
2911
2912 bolt->health = 1;
2913 bolt->takedamage = qtrue;
2914 bolt->pain = DetPackPain;
2915 bolt->die = DetPackDie;
2916
2917 bolt->s.weapon = WP_DET_PACK;
2918
2919 bolt->setTime = level.time;
2920
2921 G_SetOrigin(bolt, start);
2922 bolt->s.pos.trType = TR_GRAVITY;
2923 VectorCopy( start, bolt->s.pos.trBase );
2924 VectorScale(dir, 300, bolt->s.pos.trDelta );
2925 bolt->s.pos.trTime = level.time;
2926
2927 bolt->s.apos.trType = TR_GRAVITY;
2928 bolt->s.apos.trTime = level.time;
2929 bolt->s.apos.trBase[YAW] = rand()%360;
2930 bolt->s.apos.trBase[PITCH] = rand()%360;
2931 bolt->s.apos.trBase[ROLL] = rand()%360;
2932
2933 if (rand()%10 < 5)
2934 {
2935 bolt->s.apos.trBase[YAW] = -bolt->s.apos.trBase[YAW];
2936 }
2937
2938 vectoangles(dir, bolt->s.angles);
2939 VectorCopy(bolt->s.angles, bolt->s.apos.trBase);
2940 VectorSet(bolt->s.apos.trDelta, 300, 0, 0 );
2941 bolt->s.apos.trTime = level.time;
2942
2943 trap->LinkEntity((sharedEntity_t *)bolt);
2944 }
2945
BlowDetpacks(gentity_t * ent)2946 void BlowDetpacks(gentity_t *ent)
2947 {
2948 gentity_t *found = NULL;
2949
2950 if ( ent->client->ps.hasDetPackPlanted )
2951 {
2952 while ( (found = G_Find( found, FOFS(classname), "detpack") ) != NULL )
2953 {//loop through all ents and blow the crap out of them!
2954 if ( found->parent == ent )
2955 {
2956 VectorCopy( found->r.currentOrigin, found->s.origin );
2957 found->think = DetPackBlow;
2958 found->nextthink = level.time + 100 + Q_flrand(0.0f, 1.0f) * 200;
2959 G_Sound( found, CHAN_BODY, G_SoundIndex("sound/weapons/detpack/warning.wav") );
2960 }
2961 }
2962 ent->client->ps.hasDetPackPlanted = qfalse;
2963 }
2964 }
RemoveDetpacks(gentity_t * ent)2965 void RemoveDetpacks(gentity_t *ent)
2966 {
2967 gentity_t *found = NULL;
2968
2969 if ( ent->client->ps.hasDetPackPlanted )
2970 {
2971 while ( (found = G_Find( found, FOFS(classname), "detpack") ) != NULL )
2972 {//loop through all ents and blow the crap out of them!
2973 if ( found->parent == ent )
2974 {
2975 VectorCopy( found->r.currentOrigin, found->s.origin );
2976 found->think = G_FreeEntity;
2977 found->nextthink = level.time;
2978 // G_Sound( found, CHAN_BODY, G_SoundIndex("sound/weapons/detpack/warning.wav") );
2979 }
2980 }
2981 ent->client->ps.hasDetPackPlanted = qfalse;
2982 }
2983 }
2984
CheatsOn(void)2985 qboolean CheatsOn(void)
2986 {
2987 if ( !sv_cheats.integer )
2988 {
2989 return qfalse;
2990 }
2991 return qtrue;
2992 }
2993
WP_DropDetPack(gentity_t * ent,qboolean alt_fire)2994 void WP_DropDetPack( gentity_t *ent, qboolean alt_fire )
2995 {
2996 gentity_t *found = NULL;
2997 int trapcount = 0;
2998 int foundDetPacks[MAX_GENTITIES] = {ENTITYNUM_NONE};
2999 int trapcount_org;
3000 int lowestTimeStamp;
3001 int removeMe;
3002 int i;
3003
3004 if ( !ent || !ent->client )
3005 {
3006 return;
3007 }
3008
3009 //limit to 10 placed at any one time
3010 //see how many there are now
3011 while ( (found = G_Find( found, FOFS(classname), "detpack" )) != NULL )
3012 {
3013 if ( found->parent != ent )
3014 {
3015 continue;
3016 }
3017 foundDetPacks[trapcount++] = found->s.number;
3018 }
3019 //now remove first ones we find until there are only 9 left
3020 found = NULL;
3021 trapcount_org = trapcount;
3022 lowestTimeStamp = level.time;
3023 while ( trapcount > 9 )
3024 {
3025 removeMe = -1;
3026 for ( i = 0; i < trapcount_org; i++ )
3027 {
3028 if ( foundDetPacks[i] == ENTITYNUM_NONE )
3029 {
3030 continue;
3031 }
3032 found = &g_entities[foundDetPacks[i]];
3033 if ( found->setTime < lowestTimeStamp )
3034 {
3035 removeMe = i;
3036 lowestTimeStamp = found->setTime;
3037 }
3038 }
3039 if ( removeMe != -1 )
3040 {
3041 //remove it... or blow it?
3042 if ( &g_entities[foundDetPacks[removeMe]] == NULL )
3043 {
3044 break;
3045 }
3046 else
3047 {
3048 if (!CheatsOn())
3049 { //Let them have unlimited if cheats are enabled
3050 G_FreeEntity( &g_entities[foundDetPacks[removeMe]] );
3051 }
3052 }
3053 foundDetPacks[removeMe] = ENTITYNUM_NONE;
3054 trapcount--;
3055 }
3056 else
3057 {
3058 break;
3059 }
3060 }
3061
3062 if ( alt_fire )
3063 {
3064 BlowDetpacks(ent);
3065 }
3066 else
3067 {
3068 AngleVectors( ent->client->ps.viewangles, forward, vright, up );
3069
3070 CalcMuzzlePoint( ent, forward, vright, up, muzzle );
3071
3072 VectorNormalize( forward );
3073 VectorMA( muzzle, -4, forward, muzzle );
3074 drop_charge( ent, muzzle, forward );
3075
3076 ent->client->ps.hasDetPackPlanted = qtrue;
3077 }
3078 }
3079
WP_FireConcussionAlt(gentity_t * ent)3080 static void WP_FireConcussionAlt( gentity_t *ent )
3081 {//a rail-gun-like beam
3082 int damage = CONC_ALT_DAMAGE, skip, traces = DISRUPTOR_ALT_TRACES;
3083 qboolean render_impact = qtrue;
3084 vec3_t start, end;
3085 vec3_t /*muzzle2,*/ dir;
3086 trace_t tr;
3087 gentity_t *traceEnt, *tent;
3088 float shotRange = 8192.0f;
3089 qboolean hitDodged = qfalse;
3090 vec3_t shot_mins, shot_maxs;
3091 int i;
3092
3093 //Shove us backwards for half a second
3094 VectorMA( ent->client->ps.velocity, -200, forward, ent->client->ps.velocity );
3095 ent->client->ps.groundEntityNum = ENTITYNUM_NONE;
3096 if ( (ent->client->ps.pm_flags&PMF_DUCKED) )
3097 {//hunkered down
3098 ent->client->ps.pm_time = 100;
3099 }
3100 else
3101 {
3102 ent->client->ps.pm_time = 250;
3103 }
3104 // ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK|PMF_TIME_NOFRICTION;
3105 //FIXME: only if on ground? So no "rocket jump"? Or: (see next FIXME)
3106 //FIXME: instead, set a forced ucmd backmove instead of this sliding
3107
3108 //VectorCopy( muzzle, muzzle2 ); // making a backup copy
3109
3110 VectorCopy( muzzle, start );
3111 WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );
3112
3113 skip = ent->s.number;
3114
3115 // if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time )
3116 // {
3117 // // in overcharge mode, so doing double damage
3118 // damage *= 2;
3119 // }
3120
3121 //Make it a little easier to hit guys at long range
3122 VectorSet( shot_mins, -1, -1, -1 );
3123 VectorSet( shot_maxs, 1, 1, 1 );
3124
3125 for ( i = 0; i < traces; i++ )
3126 {
3127 VectorMA( start, shotRange, forward, end );
3128
3129 //NOTE: if you want to be able to hit guys in emplaced guns, use "G2_COLLIDE, 10" instead of "G2_RETURNONHIT, 0"
3130 //alternately, if you end up hitting an emplaced_gun that has a sitter, just redo this one trace with the "G2_COLLIDE, 10" to see if we it the sitter
3131 //trap->trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT, G2_COLLIDE, 10 );//G2_RETURNONHIT, 0 );
3132 if (d_projectileGhoul2Collision.integer)
3133 {
3134 trap->Trace( &tr, start, shot_mins, shot_maxs, end, skip, MASK_SHOT, qfalse, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
3135 }
3136 else
3137 {
3138 trap->Trace( &tr, start, shot_mins, shot_maxs, end, skip, MASK_SHOT, qfalse, 0, 0 );
3139 }
3140
3141 traceEnt = &g_entities[tr.entityNum];
3142
3143 if (d_projectileGhoul2Collision.integer && traceEnt->inuse && traceEnt->client)
3144 { //g2 collision checks -rww
3145 if (traceEnt->inuse && traceEnt->client && traceEnt->ghoul2)
3146 { //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with.
3147 traceEnt->client->g2LastSurfaceHit = tr.surfaceFlags;
3148 traceEnt->client->g2LastSurfaceTime = level.time;
3149 }
3150
3151 if (traceEnt->ghoul2)
3152 {
3153 tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here.
3154 }
3155 }
3156 if ( tr.surfaceFlags & SURF_NOIMPACT )
3157 {
3158 render_impact = qfalse;
3159 }
3160
3161 if ( tr.entityNum == ent->s.number )
3162 {
3163 // should never happen, but basically we don't want to consider a hit to ourselves?
3164 // Get ready for an attempt to trace through another person
3165 //VectorCopy( tr.endpos, muzzle2 );
3166 VectorCopy( tr.endpos, start );
3167 skip = tr.entityNum;
3168 #ifdef _DEBUG
3169 Com_Printf( "BAD! Concussion gun shot somehow traced back and hit the owner!\n" );
3170 #endif
3171 continue;
3172 }
3173
3174 // always render a shot beam, doing this the old way because I don't much feel like overriding the effect.
3175 //NOTE: let's just draw one beam at the end
3176 //tent = G_TempEntity( tr.endpos, EV_CONC_ALT_SHOT );
3177 //tent->svFlags |= SVF_BROADCAST;
3178
3179 //VectorCopy( muzzle2, tent->s.origin2 );
3180
3181 if ( tr.fraction >= 1.0f )
3182 {
3183 // draw the beam but don't do anything else
3184 break;
3185 }
3186
3187 if ( traceEnt->s.weapon == WP_SABER )//&& traceEnt->NPC
3188 {//FIXME: need a more reliable way to know we hit a jedi?
3189 hitDodged = Jedi_DodgeEvasion( traceEnt, ent, &tr, HL_NONE );
3190 //acts like we didn't even hit him
3191 }
3192 if ( !hitDodged )
3193 {
3194 if ( render_impact )
3195 {
3196 if (( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
3197 || !Q_stricmp( traceEnt->classname, "misc_model_breakable" )
3198 || traceEnt->s.eType == ET_MOVER )
3199 {
3200 qboolean noKnockBack;
3201
3202 // Create a simple impact type mark that doesn't last long in the world
3203 //G_PlayEffectID( G_EffectIndex( "concussion/alt_hit" ), tr.endpos, tr.plane.normal );
3204 //no no no
3205
3206 if ( traceEnt->client && LogAccuracyHit( traceEnt, ent ))
3207 {//NOTE: hitting multiple ents can still get you over 100% accuracy
3208 ent->client->accuracy_hits++;
3209 }
3210
3211 noKnockBack = (traceEnt->flags&FL_NO_KNOCKBACK);//will be set if they die, I want to know if it was on *before* they died
3212 if ( traceEnt && traceEnt->client && traceEnt->client->NPC_class == CLASS_GALAKMECH )
3213 {//hehe
3214 G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_CONC_ALT );
3215 break;
3216 }
3217 G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_CONC_ALT );
3218
3219 //do knockback and knockdown manually
3220 if ( traceEnt->client )
3221 {//only if we hit a client
3222 vec3_t pushDir;
3223 VectorCopy( forward, pushDir );
3224 if ( pushDir[2] < 0.2f )
3225 {
3226 pushDir[2] = 0.2f;
3227 }//hmm, re-normalize? nah...
3228 /*
3229 if ( !noKnockBack )
3230 {//knock-backable
3231 G_Throw( traceEnt, pushDir, 200 );
3232 }
3233 */
3234 if ( traceEnt->health > 0 )
3235 {//alive
3236 //if ( G_HasKnockdownAnims( traceEnt ) )
3237 if (!noKnockBack && !traceEnt->localAnimIndex && traceEnt->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN &&
3238 BG_KnockDownable(&traceEnt->client->ps)) //just check for humanoids..
3239 {//knock-downable
3240 //G_Knockdown( traceEnt, ent, pushDir, 400, qtrue );
3241 vec3_t plPDif;
3242 float pStr;
3243
3244 //cap it and stuff, base the strength and whether or not we can knockdown on the distance
3245 //from the shooter to the target
3246 VectorSubtract(traceEnt->client->ps.origin, ent->client->ps.origin, plPDif);
3247 pStr = 500.0f-VectorLength(plPDif);
3248 if (pStr < 150.0f)
3249 {
3250 pStr = 150.0f;
3251 }
3252 if (pStr > 200.0f)
3253 {
3254 traceEnt->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN;
3255 traceEnt->client->ps.forceHandExtendTime = level.time + 1100;
3256 traceEnt->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim
3257 }
3258 traceEnt->client->ps.otherKiller = ent->s.number;
3259 traceEnt->client->ps.otherKillerTime = level.time + 5000;
3260 traceEnt->client->ps.otherKillerDebounceTime = level.time + 100;
3261
3262 traceEnt->client->ps.velocity[0] += pushDir[0]*pStr;
3263 traceEnt->client->ps.velocity[1] += pushDir[1]*pStr;
3264 traceEnt->client->ps.velocity[2] = pStr;
3265 }
3266 }
3267 }
3268
3269 if ( traceEnt->s.eType == ET_MOVER )
3270 {//stop the traces on any mover
3271 break;
3272 }
3273 }
3274 else
3275 {
3276 // we only make this mark on things that can't break or move
3277 // tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS);
3278 // tent->s.eventParm = DirToByte(tr.plane.normal);
3279 // tent->s.eFlags |= EF_ALT_FIRING;
3280
3281 //tent->svFlags |= SVF_BROADCAST;
3282 //eh? why broadcast?
3283 // VectorCopy( tr.plane.normal, tent->pos1 );
3284
3285 //mmm..no..don't do this more than once for no reason whatsoever.
3286 break; // hit solid, but doesn't take damage, so stop the shot...we _could_ allow it to shoot through walls, might be cool?
3287 }
3288 }
3289 else // not rendering impact, must be a skybox or other similar thing?
3290 {
3291 break; // don't try anymore traces
3292 }
3293 }
3294 // Get ready for an attempt to trace through another person
3295 //VectorCopy( tr.endpos, muzzle2 );
3296 VectorCopy( tr.endpos, start );
3297 skip = tr.entityNum;
3298 hitDodged = qfalse;
3299 }
3300 //just draw one beam all the way to the end
3301 // tent = G_TempEntity( tr.endpos, EV_CONC_ALT_SHOT );
3302 // tent->svFlags |= SVF_BROADCAST;
3303 //again, why broadcast?
3304
3305 // tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS);
3306 // tent->s.eventParm = DirToByte(tr.plane.normal);
3307 // tent->s.eFlags |= EF_ALT_FIRING;
3308 // VectorCopy( muzzle, tent->s.origin2 );
3309
3310 // now go along the trail and make sight events
3311 VectorSubtract( tr.endpos, muzzle, dir );
3312
3313 // shotDist = VectorNormalize( dir );
3314
3315 //let's pack all this junk into a single tempent, and send it off.
3316 tent = G_TempEntity(tr.endpos, EV_CONC_ALT_IMPACT);
3317 tent->s.eventParm = DirToByte(tr.plane.normal);
3318 tent->s.owner = ent->s.number;
3319 VectorCopy(dir, tent->s.angles);
3320 VectorCopy(muzzle, tent->s.origin2);
3321 VectorCopy(forward, tent->s.angles2);
3322
3323 #if 0 //yuck
3324 //FIXME: if shoot *really* close to someone, the alert could be way out of their FOV
3325 for ( dist = 0; dist < shotDist; dist += 64 )
3326 {
3327 //FIXME: on a really long shot, this could make a LOT of alerts in one frame...
3328 VectorMA( muzzle, dist, dir, spot );
3329 AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
3330 //FIXME: creates *way* too many effects, make it one effect somehow?
3331 G_PlayEffectID( G_EffectIndex( "concussion/alt_ring" ), spot, actualAngles );
3332 }
3333 //FIXME: spawn a temp ent that continuously spawns sight alerts here? And 1 sound alert to draw their attention?
3334 VectorMA( start, shotDist-4, forward, spot );
3335 AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
3336
3337 G_PlayEffectID( G_EffectIndex( "concussion/altmuzzle_flash" ), muzzle, forward );
3338 #endif
3339 }
3340
WP_FireConcussion(gentity_t * ent)3341 static void WP_FireConcussion( gentity_t *ent )
3342 {//a fast rocket-like projectile
3343 vec3_t start;
3344 int damage = CONC_DAMAGE;
3345 float vel = CONC_VELOCITY;
3346 gentity_t *missile;
3347
3348 //hold us still for a bit
3349 //ent->client->ps.pm_time = 300;
3350 //ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
3351 //add viewkick
3352 // if ( ent->s.number < MAX_CLIENTS//player only
3353 // && !cg.renderingThirdPerson )//gives an advantage to being in 3rd person, but would look silly otherwise
3354 // {//kick the view back
3355 // cg.kick_angles[PITCH] = Q_flrand( -10, -15 );
3356 // cg.kick_time = level.time;
3357 // }
3358 //mm..yeah..this needs some reworking for mp
3359
3360 VectorCopy( muzzle, start );
3361 WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall
3362
3363 missile = CreateMissile( start, forward, vel, 10000, ent, qfalse );
3364
3365 missile->classname = "conc_proj";
3366 missile->s.weapon = WP_CONCUSSION;
3367 missile->mass = 10;
3368
3369 // Make it easier to hit things
3370 VectorSet( missile->r.maxs, ROCKET_SIZE, ROCKET_SIZE, ROCKET_SIZE );
3371 VectorScale( missile->r.maxs, -1, missile->r.mins );
3372
3373 missile->damage = damage;
3374 missile->dflags = DAMAGE_EXTRA_KNOCKBACK;
3375
3376 missile->methodOfDeath = MOD_CONC;
3377 missile->splashMethodOfDeath = MOD_CONC;
3378
3379 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
3380 missile->splashDamage = CONC_SPLASH_DAMAGE;
3381 missile->splashRadius = CONC_SPLASH_RADIUS;
3382
3383 // we don't want it to ever bounce
3384 missile->bounceCount = 0;
3385 }
3386
3387
3388 //---------------------------------------------------------
3389 // FireStunBaton
3390 //---------------------------------------------------------
WP_FireStunBaton(gentity_t * ent,qboolean alt_fire)3391 void WP_FireStunBaton( gentity_t *ent, qboolean alt_fire )
3392 {
3393 gentity_t *tr_ent;
3394 trace_t tr;
3395 vec3_t mins, maxs, end;
3396 vec3_t muzzleStun;
3397
3398 if (!ent->client)
3399 {
3400 VectorCopy(ent->r.currentOrigin, muzzleStun);
3401 muzzleStun[2] += 8;
3402 }
3403 else
3404 {
3405 VectorCopy(ent->client->ps.origin, muzzleStun);
3406 muzzleStun[2] += ent->client->ps.viewheight-6;
3407 }
3408
3409 VectorMA(muzzleStun, 20.0f, forward, muzzleStun);
3410 VectorMA(muzzleStun, 4.0f, vright, muzzleStun);
3411
3412 VectorMA( muzzleStun, STUN_BATON_RANGE, forward, end );
3413
3414 VectorSet( maxs, 6, 6, 6 );
3415 VectorScale( maxs, -1, mins );
3416
3417 trap->Trace ( &tr, muzzleStun, mins, maxs, end, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
3418
3419 if ( tr.entityNum >= ENTITYNUM_WORLD )
3420 {
3421 return;
3422 }
3423
3424 tr_ent = &g_entities[tr.entityNum];
3425
3426 if (tr_ent && tr_ent->takedamage && tr_ent->client)
3427 { //see if either party is involved in a duel
3428 if (tr_ent->client->ps.duelInProgress &&
3429 tr_ent->client->ps.duelIndex != ent->s.number)
3430 {
3431 return;
3432 }
3433
3434 if (ent->client &&
3435 ent->client->ps.duelInProgress &&
3436 ent->client->ps.duelIndex != tr_ent->s.number)
3437 {
3438 return;
3439 }
3440 }
3441
3442 if ( tr_ent && tr_ent->takedamage )
3443 {
3444 G_PlayEffect( EFFECT_STUNHIT, tr.endpos, tr.plane.normal );
3445
3446 G_Sound( tr_ent, CHAN_WEAPON, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) );
3447 G_Damage( tr_ent, ent, ent, forward, tr.endpos, STUN_BATON_DAMAGE, (DAMAGE_NO_KNOCKBACK|DAMAGE_HALF_ABSORB), MOD_STUN_BATON );
3448
3449 if (tr_ent->client)
3450 { //if it's a player then use the shock effect
3451 if ( tr_ent->client->NPC_class == CLASS_VEHICLE )
3452 {//not on vehicles
3453 if ( !tr_ent->m_pVehicle
3454 || tr_ent->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL
3455 || tr_ent->m_pVehicle->m_pVehicleInfo->type == VH_FLIER )
3456 {//can zap animals
3457 tr_ent->client->ps.electrifyTime = level.time + Q_irand( 3000, 4000 );
3458 }
3459 }
3460 else
3461 {
3462 tr_ent->client->ps.electrifyTime = level.time + 700;
3463 }
3464 }
3465 }
3466 }
3467
3468
3469 //---------------------------------------------------------
3470 // FireMelee
3471 //---------------------------------------------------------
WP_FireMelee(gentity_t * ent,qboolean alt_fire)3472 void WP_FireMelee( gentity_t *ent, qboolean alt_fire )
3473 {
3474 gentity_t *tr_ent;
3475 trace_t tr;
3476 vec3_t mins, maxs, end;
3477 vec3_t muzzlePunch;
3478
3479 if (ent->client && ent->client->ps.torsoAnim == BOTH_MELEE2)
3480 { //right
3481 if (ent->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM))
3482 {
3483 return;
3484 }
3485 }
3486 else
3487 { //left
3488 if (ent->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM))
3489 {
3490 return;
3491 }
3492 }
3493
3494 if (!ent->client)
3495 {
3496 VectorCopy(ent->r.currentOrigin, muzzlePunch);
3497 muzzlePunch[2] += 8;
3498 }
3499 else
3500 {
3501 VectorCopy(ent->client->ps.origin, muzzlePunch);
3502 muzzlePunch[2] += ent->client->ps.viewheight-6;
3503 }
3504
3505 VectorMA(muzzlePunch, 20.0f, forward, muzzlePunch);
3506 VectorMA(muzzlePunch, 4.0f, vright, muzzlePunch);
3507
3508 VectorMA( muzzlePunch, MELEE_RANGE, forward, end );
3509
3510 VectorSet( maxs, 6, 6, 6 );
3511 VectorScale( maxs, -1, mins );
3512
3513 trap->Trace ( &tr, muzzlePunch, mins, maxs, end, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
3514
3515 if (tr.entityNum != ENTITYNUM_NONE)
3516 { //hit something
3517 tr_ent = &g_entities[tr.entityNum];
3518
3519 G_Sound( ent, CHAN_AUTO, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) );
3520
3521 if (tr_ent->takedamage && tr_ent->client)
3522 { //special duel checks
3523 if (tr_ent->client->ps.duelInProgress &&
3524 tr_ent->client->ps.duelIndex != ent->s.number)
3525 {
3526 return;
3527 }
3528
3529 if (ent->client &&
3530 ent->client->ps.duelInProgress &&
3531 ent->client->ps.duelIndex != tr_ent->s.number)
3532 {
3533 return;
3534 }
3535 }
3536
3537 if ( tr_ent->takedamage )
3538 { //damage them, do more damage if we're in the second right hook
3539 int dmg = MELEE_SWING1_DAMAGE;
3540
3541 if (ent->client && ent->client->ps.torsoAnim == BOTH_MELEE2)
3542 { //do a tad bit more damage on the second swing
3543 dmg = MELEE_SWING2_DAMAGE;
3544 }
3545
3546 if ( G_HeavyMelee( ent ) )
3547 { //2x damage for heavy melee class
3548 dmg *= 2;
3549 }
3550
3551 G_Damage( tr_ent, ent, ent, forward, tr.endpos, dmg, DAMAGE_NO_ARMOR, MOD_MELEE );
3552 }
3553 }
3554 }
3555
3556 ////////////////////////////////////////////////////////////////////////////
3557 ////////////////////////////////////////////////////////////////////////////
3558 ////////////////////////////////////////////////////////////////////////////
3559 ////////////////////////////////////////////////////////////////////////////
3560 ////////////////////////////////////////////////////////////////////////////
3561
3562
3563 /*
3564 ======================
3565 SnapVectorTowards
3566
3567 Round a vector to integers for more efficient network
3568 transmission, but make sure that it rounds towards a given point
3569 rather than blindly truncating. This prevents it from truncating
3570 into a wall.
3571 ======================
3572 */
SnapVectorTowards(vec3_t v,vec3_t to)3573 void SnapVectorTowards( vec3_t v, vec3_t to ) {
3574 int i;
3575
3576 for ( i = 0 ; i < 3 ; i++ ) {
3577 if ( to[i] <= v[i] ) {
3578 v[i] = floorf( v[i] );
3579 } else {
3580 v[i] = ceilf( v[i] );
3581 }
3582 }
3583 }
3584
3585
3586 //======================================================================
3587
3588
3589 /*
3590 ===============
3591 LogAccuracyHit
3592 ===============
3593 */
LogAccuracyHit(gentity_t * target,gentity_t * attacker)3594 qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) {
3595 if( !target->takedamage ) {
3596 return qfalse;
3597 }
3598
3599 if ( target == attacker ) {
3600 return qfalse;
3601 }
3602
3603 if( !target->client ) {
3604 return qfalse;
3605 }
3606
3607 if (!attacker)
3608 {
3609 return qfalse;
3610 }
3611
3612 if( !attacker->client ) {
3613 return qfalse;
3614 }
3615
3616 if( target->client->ps.stats[STAT_HEALTH] <= 0 ) {
3617 return qfalse;
3618 }
3619
3620 if ( OnSameTeam( target, attacker ) ) {
3621 return qfalse;
3622 }
3623
3624 return qtrue;
3625 }
3626
3627
3628 /*
3629 ===============
3630 CalcMuzzlePoint
3631
3632 set muzzle location relative to pivoting eye
3633 rwwFIXMEFIXME: Since ghoul2 models are on server and properly updated now,
3634 it may be reasonable to base muzzle point off actual weapon bolt point.
3635 The down side would be that it does not necessarily look alright from a
3636 first person perspective.
3637 ===============
3638 */
CalcMuzzlePoint(gentity_t * ent,const vec3_t inForward,const vec3_t inRight,const vec3_t inUp,vec3_t muzzlePoint)3639 void CalcMuzzlePoint ( gentity_t *ent, const vec3_t inForward, const vec3_t inRight, const vec3_t inUp, vec3_t muzzlePoint )
3640 {
3641 int weapontype;
3642 vec3_t muzzleOffPoint;
3643
3644 weapontype = ent->s.weapon;
3645 VectorCopy( ent->s.pos.trBase, muzzlePoint );
3646
3647 VectorCopy(WP_MuzzlePoint[weapontype], muzzleOffPoint);
3648
3649 if (weapontype > WP_NONE && weapontype < WP_NUM_WEAPONS)
3650 { // Use the table to generate the muzzlepoint;
3651 { // Crouching. Use the add-to-Z method to adjust vertically.
3652 VectorMA(muzzlePoint, muzzleOffPoint[0], inForward, muzzlePoint);
3653 VectorMA(muzzlePoint, muzzleOffPoint[1], inRight, muzzlePoint);
3654 muzzlePoint[2] += ent->client->ps.viewheight + muzzleOffPoint[2];
3655 }
3656 }
3657
3658 // snap to integer coordinates for more efficient network bandwidth usage
3659 SnapVector( muzzlePoint );
3660 }
3661
3662 extern void G_MissileImpact( gentity_t *ent, trace_t *trace );
WP_TouchVehMissile(gentity_t * ent,gentity_t * other,trace_t * trace)3663 void WP_TouchVehMissile( gentity_t *ent, gentity_t *other, trace_t *trace )
3664 {
3665 trace_t myTrace;
3666 memcpy( (void *)&myTrace, (void *)trace, sizeof(myTrace) );
3667 if ( other )
3668 {
3669 myTrace.entityNum = other->s.number;
3670 }
3671 G_MissileImpact( ent, &myTrace );
3672 }
3673
WP_CalcVehMuzzle(gentity_t * ent,int muzzleNum)3674 void WP_CalcVehMuzzle(gentity_t *ent, int muzzleNum)
3675 {
3676 Vehicle_t *pVeh = ent->m_pVehicle;
3677 mdxaBone_t boltMatrix;
3678 vec3_t vehAngles;
3679
3680 assert(pVeh);
3681
3682 if (pVeh->m_iMuzzleTime[muzzleNum] == level.time)
3683 { //already done for this frame, don't need to do it again
3684 return;
3685 }
3686 //Uh... how about we set this, hunh...? :)
3687 pVeh->m_iMuzzleTime[muzzleNum] = level.time;
3688
3689 VectorCopy( ent->client->ps.viewangles, vehAngles );
3690 if ( pVeh->m_pVehicleInfo
3691 && (pVeh->m_pVehicleInfo->type == VH_ANIMAL
3692 ||pVeh->m_pVehicleInfo->type == VH_WALKER
3693 ||pVeh->m_pVehicleInfo->type == VH_SPEEDER) )
3694 {
3695 vehAngles[PITCH] = vehAngles[ROLL] = 0;
3696 }
3697
3698 trap->G2API_GetBoltMatrix_NoRecNoRot(ent->ghoul2, 0, pVeh->m_iMuzzleTag[muzzleNum], &boltMatrix, vehAngles,
3699 ent->client->ps.origin, level.time, NULL, ent->modelScale);
3700 BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, pVeh->m_vMuzzlePos[muzzleNum]);
3701 BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, pVeh->m_vMuzzleDir[muzzleNum]);
3702 }
3703
WP_VehWeapSetSolidToOwner(gentity_t * self)3704 void WP_VehWeapSetSolidToOwner( gentity_t *self )
3705 {
3706 self->r.svFlags |= SVF_OWNERNOTSHARED;
3707 if ( self->genericValue1 )
3708 {//expire after a time
3709 if ( self->genericValue2 )
3710 {//blow up when your lifetime is up
3711 self->think = G_ExplodeMissile;//FIXME: custom func?
3712 }
3713 else
3714 {//just remove yourself
3715 self->think = G_FreeEntity;//FIXME: custom func?
3716 }
3717 self->nextthink = level.time + self->genericValue1;
3718 }
3719 }
3720
3721 #define VEH_HOMING_MISSILE_THINK_TIME 100
WP_FireVehicleWeapon(gentity_t * ent,vec3_t start,vec3_t dir,vehWeaponInfo_t * vehWeapon,qboolean alt_fire,qboolean isTurretWeap)3722 gentity_t *WP_FireVehicleWeapon( gentity_t *ent, vec3_t start, vec3_t dir, vehWeaponInfo_t *vehWeapon, qboolean alt_fire, qboolean isTurretWeap )
3723 {
3724 gentity_t *missile = NULL;
3725
3726 //FIXME: add some randomness...? Inherent inaccuracy stat of weapon? Pilot skill?
3727 if ( !vehWeapon )
3728 {//invalid vehicle weapon
3729 return NULL;
3730 }
3731 else if ( vehWeapon->bIsProjectile )
3732 {//projectile entity
3733 vec3_t mins, maxs;
3734
3735 VectorSet( maxs, vehWeapon->fWidth/2.0f,vehWeapon->fWidth/2.0f,vehWeapon->fHeight/2.0f );
3736 VectorScale( maxs, -1, mins );
3737
3738 //make sure our start point isn't on the other side of a wall
3739 WP_TraceSetStart( ent, start, mins, maxs );
3740
3741 //FIXME: CUSTOM MODEL?
3742 //QUERY: alt_fire true or not? Does it matter?
3743 missile = CreateMissile( start, dir, vehWeapon->fSpeed, 10000, ent, qfalse );
3744
3745 missile->classname = "vehicle_proj";
3746
3747 missile->s.genericenemyindex = ent->s.number+MAX_GENTITIES;
3748 missile->damage = vehWeapon->iDamage;
3749 missile->splashDamage = vehWeapon->iSplashDamage;
3750 missile->splashRadius = vehWeapon->fSplashRadius;
3751
3752 //FIXME: externalize some of these properties?
3753 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
3754 missile->clipmask = MASK_SHOT;
3755 //Maybe by checking flags...?
3756 if ( vehWeapon->bSaberBlockable )
3757 {
3758 missile->clipmask |= CONTENTS_LIGHTSABER;
3759 }
3760 /*
3761 if ( (vehWeapon->iFlags&VWF_KNOCKBACK) )
3762 {
3763 missile->dflags &= ~DAMAGE_DEATH_KNOCKBACK;
3764 }
3765 if ( (vehWeapon->iFlags&VWF_RADAR) )
3766 {
3767 missile->s.eFlags |= EF_RADAROBJECT;
3768 }
3769 */
3770 // Make it easier to hit things
3771 VectorCopy( mins, missile->r.mins );
3772 VectorCopy( maxs, missile->r.maxs );
3773 //some slightly different stuff for things with bboxes
3774 if ( vehWeapon->fWidth || vehWeapon->fHeight )
3775 {//we assume it's a rocket-like thing
3776 missile->s.weapon = WP_ROCKET_LAUNCHER;//does this really matter?
3777 missile->methodOfDeath = MOD_VEHICLE;//MOD_ROCKET;
3778 missile->splashMethodOfDeath = MOD_VEHICLE;//MOD_ROCKET;// ?SPLASH;
3779
3780 // we don't want it to ever bounce
3781 missile->bounceCount = 0;
3782
3783 missile->mass = 10;
3784 }
3785 else
3786 {//a blaster-laser-like thing
3787 missile->s.weapon = WP_BLASTER;//does this really matter?
3788 missile->methodOfDeath = MOD_VEHICLE; //count as a heavy weap
3789 missile->splashMethodOfDeath = MOD_VEHICLE;// ?SPLASH;
3790 // we don't want it to bounce forever
3791 missile->bounceCount = 8;
3792 }
3793
3794 if ( vehWeapon->bHasGravity )
3795 {//TESTME: is this all we need to do?
3796 missile->s.weapon = WP_THERMAL;//does this really matter?
3797 missile->s.pos.trType = TR_GRAVITY;
3798 }
3799
3800 if ( vehWeapon->bIonWeapon )
3801 {//so it disables ship shields and sends them out of control
3802 missile->s.weapon = WP_DEMP2;
3803 }
3804
3805 if ( vehWeapon->iHealth )
3806 {//the missile can take damage
3807 missile->health = vehWeapon->iHealth;
3808 missile->takedamage = qtrue;
3809 missile->r.contents = MASK_SHOT;
3810 missile->die = RocketDie;
3811 }
3812
3813 //pilot should own this projectile on server if we have a pilot
3814 if (ent->m_pVehicle && ent->m_pVehicle->m_pPilot)
3815 {//owned by vehicle pilot
3816 missile->r.ownerNum = ent->m_pVehicle->m_pPilot->s.number;
3817 }
3818 else
3819 {//owned by vehicle?
3820 missile->r.ownerNum = ent->s.number;
3821 }
3822
3823 //set veh as cgame side owner for purpose of fx overrides
3824 missile->s.owner = ent->s.number;
3825 if ( alt_fire )
3826 {//use the second weapon's iShotFX
3827 missile->s.eFlags |= EF_ALT_FIRING;
3828 }
3829 if ( isTurretWeap )
3830 {//look for the turret weapon info on cgame side, not vehicle weapon info
3831 missile->s.weapon = WP_TURRET;
3832 }
3833 if ( vehWeapon->iLifeTime )
3834 {//expire after a time
3835 if ( vehWeapon->bExplodeOnExpire )
3836 {//blow up when your lifetime is up
3837 missile->think = G_ExplodeMissile;//FIXME: custom func?
3838 }
3839 else
3840 {//just remove yourself
3841 missile->think = G_FreeEntity;//FIXME: custom func?
3842 }
3843 missile->nextthink = level.time + vehWeapon->iLifeTime;
3844 }
3845 missile->s.otherEntityNum2 = (vehWeapon-&g_vehWeaponInfo[0]);
3846 missile->s.eFlags |= EF_JETPACK_ACTIVE;
3847 //homing
3848 if ( vehWeapon->fHoming )
3849 {//homing missile
3850 if ( ent->client && ent->client->ps.rocketLockIndex != ENTITYNUM_NONE )
3851 {
3852 int dif = 0;
3853 float rTime;
3854 rTime = ent->client->ps.rocketLockTime;
3855
3856 if (rTime == -1)
3857 {
3858 rTime = ent->client->ps.rocketLastValidTime;
3859 }
3860
3861 if ( !vehWeapon->iLockOnTime )
3862 {//no minimum lock-on time
3863 dif = 10;//guaranteed lock-on
3864 }
3865 else
3866 {
3867 float lockTimeInterval = vehWeapon->iLockOnTime/16.0f;
3868 dif = ( level.time - rTime ) / lockTimeInterval;
3869 }
3870
3871 if (dif < 0)
3872 {
3873 dif = 0;
3874 }
3875
3876 //It's 10 even though it locks client-side at 8, because we want them to have a sturdy lock first, and because there's a slight difference in time between server and client
3877 if ( dif >= 10 && rTime != -1 )
3878 {
3879 missile->enemy = &g_entities[ent->client->ps.rocketLockIndex];
3880
3881 if (missile->enemy && missile->enemy->client && missile->enemy->health > 0 && !OnSameTeam(ent, missile->enemy))
3882 { //if enemy became invalid, died, or is on the same team, then don't seek it
3883 missile->spawnflags |= 1;//just to let it know it should be faster...
3884 missile->speed = vehWeapon->fSpeed;
3885 missile->angle = vehWeapon->fHoming;
3886 missile->radius = vehWeapon->fHomingFOV;
3887 //crap, if we have a lifetime, need to store that somewhere else on ent and have rocketThink func check it every frame...
3888 if ( vehWeapon->iLifeTime )
3889 {//expire after a time
3890 missile->genericValue1 = level.time + vehWeapon->iLifeTime;
3891 missile->genericValue2 = (int)(vehWeapon->bExplodeOnExpire);
3892 }
3893 //now go ahead and use the rocketThink func
3894 missile->think = rocketThink;//FIXME: custom func?
3895 missile->nextthink = level.time + VEH_HOMING_MISSILE_THINK_TIME;
3896 missile->s.eFlags |= EF_RADAROBJECT;//FIXME: externalize
3897 if ( missile->enemy->s.NPC_class == CLASS_VEHICLE )
3898 {//let vehicle know we've locked on to them
3899 missile->s.otherEntityNum = missile->enemy->s.number;
3900 }
3901 }
3902 }
3903
3904 VectorCopy( dir, missile->movedir );
3905 missile->random = 1.0f;//FIXME: externalize?
3906 }
3907 }
3908 if ( !vehWeapon->fSpeed )
3909 {//a mine or something?
3910 //only do damage when someone touches us
3911 missile->s.weapon = WP_THERMAL;//does this really matter?
3912 G_SetOrigin( missile, start );
3913 missile->touch = WP_TouchVehMissile;
3914 missile->s.eFlags |= EF_RADAROBJECT;//FIXME: externalize
3915 //crap, if we have a lifetime, need to store that somewhere else on ent and have rocketThink func check it every frame...
3916 if ( vehWeapon->iLifeTime )
3917 {//expire after a time
3918 missile->genericValue1 = vehWeapon->iLifeTime;
3919 missile->genericValue2 = (int)(vehWeapon->bExplodeOnExpire);
3920 }
3921 //now go ahead and use the setsolidtoowner func
3922 missile->think = WP_VehWeapSetSolidToOwner;
3923 missile->nextthink = level.time + 3000;
3924 }
3925 }
3926 else
3927 {//traceline
3928 //FIXME: implement
3929 }
3930
3931 return missile;
3932 }
3933
3934 //custom routine to not waste tempents horribly -rww
G_VehMuzzleFireFX(gentity_t * ent,gentity_t * broadcaster,int muzzlesFired)3935 void G_VehMuzzleFireFX( gentity_t *ent, gentity_t *broadcaster, int muzzlesFired )
3936 {
3937 Vehicle_t *pVeh = ent->m_pVehicle;
3938 gentity_t *b;
3939
3940 if (!pVeh)
3941 {
3942 return;
3943 }
3944
3945 if (!broadcaster)
3946 { //oh well. We will WASTE A TEMPENT.
3947 b = G_TempEntity( ent->client->ps.origin, EV_VEH_FIRE );
3948 }
3949 else
3950 { //joy
3951 b = broadcaster;
3952 }
3953
3954 //this guy owns it
3955 b->s.owner = ent->s.number;
3956
3957 //this is the bitfield of all muzzles fired this time
3958 //NOTE: just need MAX_VEHICLE_MUZZLES bits for this... should be cool since it's currently 12 and we're sending it in 16 bits
3959 b->s.trickedentindex = muzzlesFired;
3960
3961 if ( broadcaster )
3962 { //add the event
3963 G_AddEvent( b, EV_VEH_FIRE, 0 );
3964 }
3965 }
3966
G_EstimateCamPos(vec3_t viewAngles,vec3_t cameraFocusLoc,float viewheight,float thirdPersonRange,float thirdPersonHorzOffset,float vertOffset,float pitchOffset,int ignoreEntNum,vec3_t camPos)3967 void G_EstimateCamPos( vec3_t viewAngles, vec3_t cameraFocusLoc, float viewheight, float thirdPersonRange,
3968 float thirdPersonHorzOffset, float vertOffset, float pitchOffset,
3969 int ignoreEntNum, vec3_t camPos )
3970 {
3971 int MASK_CAMERACLIP = (MASK_SOLID|CONTENTS_PLAYERCLIP);
3972 float CAMERA_SIZE = 4;
3973 vec3_t cameramins;
3974 vec3_t cameramaxs;
3975 vec3_t cameraFocusAngles, camerafwd, cameraup;
3976 vec3_t cameraIdealTarget, cameraCurTarget;
3977 vec3_t cameraIdealLoc, cameraCurLoc;
3978 vec3_t diff;
3979 vec3_t camAngles;
3980 matrix3_t viewaxis;
3981 trace_t trace;
3982
3983 VectorSet( cameramins, -CAMERA_SIZE, -CAMERA_SIZE, -CAMERA_SIZE );
3984 VectorSet( cameramaxs, CAMERA_SIZE, CAMERA_SIZE, CAMERA_SIZE );
3985
3986 VectorCopy( viewAngles, cameraFocusAngles );
3987 cameraFocusAngles[PITCH] += pitchOffset;
3988 if ( !bg_fighterAltControl.integer )
3989 {//clamp view pitch
3990 cameraFocusAngles[PITCH] = AngleNormalize180( cameraFocusAngles[PITCH] );
3991 if (cameraFocusAngles[PITCH] > 80.0)
3992 {
3993 cameraFocusAngles[PITCH] = 80.0;
3994 }
3995 else if (cameraFocusAngles[PITCH] < -80.0)
3996 {
3997 cameraFocusAngles[PITCH] = -80.0;
3998 }
3999 }
4000 AngleVectors(cameraFocusAngles, camerafwd, NULL, cameraup);
4001
4002 cameraFocusLoc[2] += viewheight;
4003
4004 VectorCopy( cameraFocusLoc, cameraIdealTarget );
4005 cameraIdealTarget[2] += vertOffset;
4006
4007 //NOTE: on cgame, this uses the thirdpersontargetdamp value, we ignore that here
4008 VectorCopy( cameraIdealTarget, cameraCurTarget );
4009 trap->Trace( &trace, cameraFocusLoc, cameramins, cameramaxs, cameraCurTarget, ignoreEntNum, MASK_CAMERACLIP, qfalse, 0, 0 );
4010 if (trace.fraction < 1.0)
4011 {
4012 VectorCopy(trace.endpos, cameraCurTarget);
4013 }
4014
4015 VectorMA(cameraIdealTarget, -(thirdPersonRange), camerafwd, cameraIdealLoc);
4016 //NOTE: on cgame, this uses the thirdpersoncameradamp value, we ignore that here
4017 VectorCopy( cameraIdealLoc, cameraCurLoc );
4018 trap->Trace(&trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, ignoreEntNum, MASK_CAMERACLIP, qfalse, 0, 0);
4019 if (trace.fraction < 1.0)
4020 {
4021 VectorCopy( trace.endpos, cameraCurLoc );
4022 }
4023
4024 VectorSubtract(cameraCurTarget, cameraCurLoc, diff);
4025 {
4026 float dist = VectorNormalize(diff);
4027 //under normal circumstances, should never be 0.00000 and so on.
4028 if ( !dist || (diff[0] == 0 || diff[1] == 0) )
4029 {//must be hitting something, need some value to calc angles, so use cam forward
4030 VectorCopy( camerafwd, diff );
4031 }
4032 }
4033
4034 vectoangles(diff, camAngles);
4035
4036 if ( thirdPersonHorzOffset != 0.0f )
4037 {
4038 AnglesToAxis( camAngles, viewaxis );
4039 VectorMA( cameraCurLoc, thirdPersonHorzOffset, viewaxis[1], cameraCurLoc );
4040 }
4041
4042 VectorCopy(cameraCurLoc, camPos);
4043 }
4044
WP_GetVehicleCamPos(gentity_t * ent,gentity_t * pilot,vec3_t camPos)4045 void WP_GetVehicleCamPos( gentity_t *ent, gentity_t *pilot, vec3_t camPos )
4046 {
4047 float thirdPersonHorzOffset = ent->m_pVehicle->m_pVehicleInfo->cameraHorzOffset;
4048 float thirdPersonRange = ent->m_pVehicle->m_pVehicleInfo->cameraRange;
4049 float pitchOffset = ent->m_pVehicle->m_pVehicleInfo->cameraPitchOffset;
4050 float vertOffset = ent->m_pVehicle->m_pVehicleInfo->cameraVertOffset;
4051
4052 if ( ent->client->ps.hackingTime )
4053 {
4054 thirdPersonHorzOffset += (((float)ent->client->ps.hackingTime)/MAX_STRAFE_TIME) * -80.0f;
4055 thirdPersonRange += fabs(((float)ent->client->ps.hackingTime)/MAX_STRAFE_TIME) * 100.0f;
4056 }
4057
4058 if ( ent->m_pVehicle->m_pVehicleInfo->cameraPitchDependantVertOffset )
4059 {
4060 if ( pilot->client->ps.viewangles[PITCH] > 0 )
4061 {
4062 vertOffset = 130+pilot->client->ps.viewangles[PITCH]*-10;
4063 if ( vertOffset < -170 )
4064 {
4065 vertOffset = -170;
4066 }
4067 }
4068 else if ( pilot->client->ps.viewangles[PITCH] < 0 )
4069 {
4070 vertOffset = 130+pilot->client->ps.viewangles[PITCH]*-5;
4071 if ( vertOffset > 130 )
4072 {
4073 vertOffset = 130;
4074 }
4075 }
4076 else
4077 {
4078 vertOffset = 30;
4079 }
4080 if ( pilot->client->ps.viewangles[PITCH] > 0 )
4081 {
4082 pitchOffset = pilot->client->ps.viewangles[PITCH]*-0.75;
4083 }
4084 else if ( pilot->client->ps.viewangles[PITCH] < 0 )
4085 {
4086 pitchOffset = pilot->client->ps.viewangles[PITCH]*-0.75;
4087 }
4088 else
4089 {
4090 pitchOffset = 0;
4091 }
4092 }
4093
4094 //Control Scheme 3 Method:
4095 G_EstimateCamPos( ent->client->ps.viewangles, pilot->client->ps.origin, pilot->client->ps.viewheight, thirdPersonRange,
4096 thirdPersonHorzOffset, vertOffset, pitchOffset,
4097 pilot->s.number, camPos );
4098 /*
4099 //Control Scheme 2 Method:
4100 G_EstimateCamPos( ent->m_pVehicle->m_vOrientation, ent->r.currentOrigin, pilot->client->ps.viewheight, thirdPersonRange,
4101 thirdPersonHorzOffset, vertOffset, pitchOffset,
4102 pilot->s.number, camPos );
4103 */
4104 }
4105
WP_VehLeadCrosshairVeh(gentity_t * camTraceEnt,vec3_t newEnd,const vec3_t dir,const vec3_t shotStart,vec3_t shotDir)4106 void WP_VehLeadCrosshairVeh( gentity_t *camTraceEnt, vec3_t newEnd, const vec3_t dir, const vec3_t shotStart, vec3_t shotDir )
4107 {
4108 if ( camTraceEnt
4109 && camTraceEnt->client
4110 && camTraceEnt->client->NPC_class == CLASS_VEHICLE )
4111 {//if the crosshair is on a vehicle, lead it
4112 float distAdjust = DotProduct( camTraceEnt->client->ps.velocity, dir );
4113 VectorMA( newEnd, distAdjust, dir, newEnd );
4114 }
4115 VectorSubtract( newEnd, shotStart, shotDir );
4116 VectorNormalize( shotDir );
4117 }
4118
4119 #define MAX_XHAIR_DIST_ACCURACY 20000.0f
4120 extern float g_cullDistance;
4121 extern int BG_VehTraceFromCamPos( trace_t *camTrace, bgEntity_t *bgEnt, const vec3_t entOrg, const vec3_t shotStart, const vec3_t end, vec3_t newEnd, vec3_t shotDir, float bestDist );
WP_VehCheckTraceFromCamPos(gentity_t * ent,const vec3_t shotStart,vec3_t shotDir)4122 qboolean WP_VehCheckTraceFromCamPos( gentity_t *ent, const vec3_t shotStart, vec3_t shotDir )
4123 {
4124 //FIXME: only if dynamicCrosshair and dynamicCrosshairPrecision is on!
4125 if ( !ent
4126 || !ent->m_pVehicle
4127 || !ent->m_pVehicle->m_pVehicleInfo
4128 || !ent->m_pVehicle->m_pPilot//not being driven
4129 || !((gentity_t*)ent->m_pVehicle->m_pPilot)->client//not being driven by a client...?!!!
4130 || (ent->m_pVehicle->m_pPilot->s.number >= MAX_CLIENTS) )//being driven, but not by a real client, no need to worry about crosshair
4131 {
4132 return qfalse;
4133 }
4134 if ( (ent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER && g_cullDistance > MAX_XHAIR_DIST_ACCURACY )
4135 || ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER)
4136 {
4137 //FIRST: simulate the normal crosshair trace from the center of the veh straight forward
4138 trace_t trace;
4139 vec3_t dir, start, end;
4140 if ( ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER )
4141 {//for some reason, the walker always draws the crosshair out from from the first muzzle point
4142 AngleVectors( ent->client->ps.viewangles, dir, NULL, NULL );
4143 VectorCopy( ent->r.currentOrigin, start );
4144 start[2] += ent->m_pVehicle->m_pVehicleInfo->height-DEFAULT_MINS_2-48;
4145 }
4146 else
4147 {
4148 vec3_t ang;
4149 if (ent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER)
4150 {
4151 VectorSet(ang, 0.0f, ent->m_pVehicle->m_vOrientation[1], 0.0f);
4152 }
4153 else
4154 {
4155 VectorCopy(ent->m_pVehicle->m_vOrientation, ang);
4156 }
4157 AngleVectors( ang, dir, NULL, NULL );
4158 VectorCopy( ent->r.currentOrigin, start );
4159 }
4160 VectorMA( start, g_cullDistance, dir, end );
4161 trap->Trace( &trace, start, vec3_origin, vec3_origin, end, ent->s.number, CONTENTS_SOLID|CONTENTS_BODY, qfalse, 0, 0 );
4162
4163 if ( ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER )
4164 {//just use the result of that one trace since walkers don't do the extra trace
4165 VectorSubtract( trace.endpos, shotStart, shotDir );
4166 VectorNormalize( shotDir );
4167 return qtrue;
4168 }
4169 else
4170 {//NOW do the trace from the camPos and compare with above trace
4171 trace_t extraTrace;
4172 vec3_t newEnd;
4173 int camTraceEntNum = BG_VehTraceFromCamPos( &extraTrace, (bgEntity_t *)ent, ent->r.currentOrigin, shotStart, end, newEnd, shotDir, (trace.fraction*g_cullDistance) );
4174 if ( camTraceEntNum )
4175 {
4176 WP_VehLeadCrosshairVeh( &g_entities[camTraceEntNum-1], newEnd, dir, shotStart, shotDir );
4177 return qtrue;
4178 }
4179 }
4180 }
4181 return qfalse;
4182 }
4183
4184 //---------------------------------------------------------
FireVehicleWeapon(gentity_t * ent,qboolean alt_fire)4185 void FireVehicleWeapon( gentity_t *ent, qboolean alt_fire )
4186 //---------------------------------------------------------
4187 {
4188 Vehicle_t *pVeh = ent->m_pVehicle;
4189 int muzzlesFired = 0;
4190 gentity_t *missile = NULL;
4191 vehWeaponInfo_t *vehWeapon = NULL;
4192 qboolean clearRocketLockEntity = qfalse;
4193
4194 if ( !pVeh )
4195 {
4196 return;
4197 }
4198
4199 if (pVeh->m_iRemovedSurfaces)
4200 { //can't fire when the thing is breaking apart
4201 return;
4202 }
4203
4204 if (pVeh->m_pVehicleInfo->type == VH_WALKER &&
4205 ent->client->ps.electrifyTime > level.time)
4206 { //don't fire while being electrocuted
4207 return;
4208 }
4209
4210 // TODO?: If possible (probably not enough time), it would be nice if secondary fire was actually a mode switch/toggle
4211 // so that, for instance, an x-wing can have 4-gun fire, or individual muzzle fire. If you wanted a different weapon, you
4212 // would actually have to press the 2 key or something like that (I doubt I'd get a graphic for it anyways though). -AReis
4213
4214 // If this is not the alternate fire, fire a normal blaster shot...
4215 if ( pVeh->m_pVehicleInfo &&
4216 (pVeh->m_pVehicleInfo->type != VH_FIGHTER || (pVeh->m_ulFlags&VEH_WINGSOPEN)) ) // NOTE: Wings open also denotes that it has already launched.
4217 {//fighters can only fire when wings are open
4218 int weaponNum = 0, vehWeaponIndex = VEH_WEAPON_NONE;
4219 int delay = 1000;
4220 qboolean aimCorrect = qfalse;
4221 qboolean linkedFiring = qfalse;
4222
4223 if ( !alt_fire )
4224 {
4225 weaponNum = 0;
4226 }
4227 else
4228 {
4229 weaponNum = 1;
4230 }
4231
4232 vehWeaponIndex = pVeh->m_pVehicleInfo->weapon[weaponNum].ID;
4233
4234 if ( pVeh->weaponStatus[weaponNum].ammo <= 0 )
4235 {//no ammo for this weapon
4236 if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
4237 {// let the client know he's out of ammo
4238 int i;
4239 //but only if one of the vehicle muzzles is actually ready to fire this weapon
4240 for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
4241 {
4242 if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
4243 {//this muzzle doesn't match the weapon we're trying to use
4244 continue;
4245 }
4246 if ( pVeh->m_iMuzzleTag[i] != -1
4247 && pVeh->m_iMuzzleWait[i] < level.time )
4248 {//this one would have fired, send the no ammo message
4249 G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
4250 break;
4251 }
4252 }
4253 }
4254 return;
4255 }
4256
4257 delay = pVeh->m_pVehicleInfo->weapon[weaponNum].delay;
4258 aimCorrect = pVeh->m_pVehicleInfo->weapon[weaponNum].aimCorrect;
4259 if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2//always linked
4260 || ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 1//optionally linkable
4261 && pVeh->weaponStatus[weaponNum].linked ) )//linked
4262 {//we're linking the primary or alternate weapons, so we'll do *all* the muzzles
4263 linkedFiring = qtrue;
4264 }
4265
4266 if ( vehWeaponIndex <= VEH_WEAPON_BASE || vehWeaponIndex >= MAX_VEH_WEAPONS )
4267 {//invalid vehicle weapon
4268 return;
4269 }
4270 else
4271 {
4272 int i, numMuzzles = 0, numMuzzlesReady = 0, cumulativeDelay = 0, cumulativeAmmo = 0;
4273 qboolean sentAmmoWarning = qfalse;
4274
4275 vehWeapon = &g_vehWeaponInfo[vehWeaponIndex];
4276
4277 if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2 )
4278 {//always linked weapons don't accumulate delay, just use specified delay
4279 cumulativeDelay = delay;
4280 }
4281 //find out how many we've got for this weapon
4282 for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
4283 {
4284 if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
4285 {//this muzzle doesn't match the weapon we're trying to use
4286 continue;
4287 }
4288 if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_iMuzzleWait[i] < level.time )
4289 {
4290 numMuzzlesReady++;
4291 }
4292 if ( pVeh->m_pVehicleInfo->weapMuzzle[pVeh->weaponStatus[weaponNum].nextMuzzle] != vehWeaponIndex )
4293 {//Our designated next muzzle for this weapon isn't valid for this weapon (happens when ships fire for the first time)
4294 //set the next to this one
4295 pVeh->weaponStatus[weaponNum].nextMuzzle = i;
4296 }
4297 if ( linkedFiring )
4298 {
4299 cumulativeAmmo += vehWeapon->iAmmoPerShot;
4300 if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable != 2 )
4301 {//always linked weapons don't accumulate delay, just use specified delay
4302 cumulativeDelay += delay;
4303 }
4304 }
4305 numMuzzles++;
4306 }
4307
4308 if ( linkedFiring )
4309 {//firing all muzzles at once
4310 if ( numMuzzlesReady != numMuzzles )
4311 {//can't fire all linked muzzles yet
4312 return;
4313 }
4314 else
4315 {//can fire all linked muzzles, check ammo
4316 if ( pVeh->weaponStatus[weaponNum].ammo < cumulativeAmmo )
4317 {//can't fire, not enough ammo
4318 if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
4319 {// let the client know he's out of ammo
4320 G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
4321 }
4322 return;
4323 }
4324 }
4325 }
4326
4327 for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
4328 {
4329 if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
4330 {//this muzzle doesn't match the weapon we're trying to use
4331 continue;
4332 }
4333 if ( !linkedFiring
4334 && i != pVeh->weaponStatus[weaponNum].nextMuzzle )
4335 {//we're only firing one muzzle and this isn't it
4336 continue;
4337 }
4338
4339 // Fire this muzzle.
4340 if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_iMuzzleWait[i] < level.time )
4341 {
4342 vec3_t start, dir;
4343
4344 if ( pVeh->weaponStatus[weaponNum].ammo < vehWeapon->iAmmoPerShot )
4345 {//out of ammo!
4346 if ( !sentAmmoWarning )
4347 {
4348 sentAmmoWarning = qtrue;
4349 if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
4350 {// let the client know he's out of ammo
4351 G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
4352 }
4353 }
4354 }
4355 else
4356 {//have enough ammo to shoot
4357 //do the firing
4358 WP_CalcVehMuzzle(ent, i);
4359 VectorCopy( pVeh->m_vMuzzlePos[i], start );
4360 VectorCopy( pVeh->m_vMuzzleDir[i], dir );
4361 if ( WP_VehCheckTraceFromCamPos( ent, start, dir ) )
4362 {//auto-aim at whatever crosshair would be over from camera's point of view (if closer)
4363 }
4364 else if ( aimCorrect )
4365 {//auto-aim the missile at the crosshair if there's anything there
4366 trace_t trace;
4367 vec3_t end;
4368 vec3_t ang;
4369 vec3_t fixedDir;
4370
4371 if (pVeh->m_pVehicleInfo->type == VH_SPEEDER)
4372 {
4373 VectorSet(ang, 0.0f, pVeh->m_vOrientation[1], 0.0f);
4374 }
4375 else
4376 {
4377 VectorCopy(pVeh->m_vOrientation, ang);
4378 }
4379 AngleVectors( ang, fixedDir, NULL, NULL );
4380 VectorMA( ent->r.currentOrigin, 32768, fixedDir, end );
4381 //VectorMA( ent->r.currentOrigin, 8192, dir, end );
4382 trap->Trace( &trace, ent->r.currentOrigin, vec3_origin, vec3_origin, end, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
4383 if ( trace.fraction < 1.0f && !trace.allsolid && !trace.startsolid )
4384 {
4385 vec3_t newEnd;
4386 VectorCopy( trace.endpos, newEnd );
4387 WP_VehLeadCrosshairVeh( &g_entities[trace.entityNum], newEnd, fixedDir, start, dir );
4388 }
4389 }
4390
4391 //play the weapon's muzzle effect if we have one
4392 //NOTE: just need MAX_VEHICLE_MUZZLES bits for this... should be cool since it's currently 12 and we're sending it in 16 bits
4393 muzzlesFired |= (1<<i);
4394
4395 missile = WP_FireVehicleWeapon( ent, start, dir, vehWeapon, alt_fire, qfalse );
4396 if ( vehWeapon->fHoming )
4397 {//clear the rocket lock entity *after* all muzzles have fired
4398 clearRocketLockEntity = qtrue;
4399 }
4400 }
4401
4402 if ( linkedFiring )
4403 {//we're linking the weapon, so continue on and fire all appropriate muzzles
4404 continue;
4405 }
4406 //else just firing one
4407 //take the ammo, set the next muzzle and set the delay on it
4408 if ( numMuzzles > 1 )
4409 {//more than one, look for it
4410 int nextMuzzle = pVeh->weaponStatus[weaponNum].nextMuzzle;
4411 while ( 1 )
4412 {
4413 nextMuzzle++;
4414 if ( nextMuzzle >= MAX_VEHICLE_MUZZLES )
4415 {
4416 nextMuzzle = 0;
4417 }
4418 if ( nextMuzzle == pVeh->weaponStatus[weaponNum].nextMuzzle )
4419 {//WTF? Wrapped without finding another valid one!
4420 break;
4421 }
4422 if ( pVeh->m_pVehicleInfo->weapMuzzle[nextMuzzle] == vehWeaponIndex )
4423 {//this is the next muzzle for this weapon
4424 pVeh->weaponStatus[weaponNum].nextMuzzle = nextMuzzle;
4425 break;
4426 }
4427 }
4428 }//else, just stay on the one we just fired
4429 //set the delay on the next muzzle
4430 pVeh->m_iMuzzleWait[pVeh->weaponStatus[weaponNum].nextMuzzle] = level.time + delay;
4431 //take away the ammo
4432 pVeh->weaponStatus[weaponNum].ammo -= vehWeapon->iAmmoPerShot;
4433 //NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array
4434 if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client )
4435 {
4436 ((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo;
4437 }
4438 //done!
4439 //we'll get in here again next frame and try the next muzzle...
4440 //return;
4441 goto tryFire;
4442 }
4443 }
4444 //we went through all the muzzles, so apply the cumulative delay and ammo cost
4445 if ( cumulativeAmmo )
4446 {//taking ammo one shot at a time
4447 //take the ammo
4448 pVeh->weaponStatus[weaponNum].ammo -= cumulativeAmmo;
4449 //NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array
4450 if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client )
4451 {
4452 ((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo;
4453 }
4454 }
4455 if ( cumulativeDelay )
4456 {//we linked muzzles so we need to apply the cumulative delay now, to each of the linked muzzles
4457 for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
4458 {
4459 if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
4460 {//this muzzle doesn't match the weapon we're trying to use
4461 continue;
4462 }
4463 //apply the cumulative delay
4464 pVeh->m_iMuzzleWait[i] = level.time + cumulativeDelay;
4465 }
4466 }
4467 }
4468 }
4469
4470 tryFire:
4471 if ( clearRocketLockEntity )
4472 {//hmm, should probably clear that anytime any weapon fires?
4473 ent->client->ps.rocketLockIndex = ENTITYNUM_NONE;
4474 ent->client->ps.rocketLockTime = 0;
4475 ent->client->ps.rocketTargetTime = 0;
4476 }
4477
4478 if ( vehWeapon && muzzlesFired > 0 )
4479 {
4480 G_VehMuzzleFireFX(ent, missile, muzzlesFired );
4481 }
4482 }
4483
4484 /*
4485 ===============
4486 FireWeapon
4487 ===============
4488 */
4489 int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint);
4490
FireWeapon(gentity_t * ent,qboolean altFire)4491 void FireWeapon( gentity_t *ent, qboolean altFire ) {
4492 // track shots taken for accuracy tracking. melee weapons are not tracked.
4493 if( ent->s.weapon != WP_SABER && ent->s.weapon != WP_STUN_BATON && ent->s.weapon != WP_MELEE )
4494 {
4495 if( ent->s.weapon == WP_FLECHETTE ) {
4496 ent->client->accuracy_shots += FLECHETTE_SHOTS;
4497 } else {
4498 ent->client->accuracy_shots++;
4499 }
4500 }
4501
4502 if ( ent && ent->client && ent->client->NPC_class == CLASS_VEHICLE )
4503 {
4504 FireVehicleWeapon( ent, altFire );
4505 return;
4506 }
4507 else
4508 {
4509 // set aiming directions
4510 if (ent->s.weapon == WP_EMPLACED_GUN &&
4511 ent->client->ps.emplacedIndex)
4512 { //if using emplaced then base muzzle point off of gun position/angles
4513 gentity_t *emp = &g_entities[ent->client->ps.emplacedIndex];
4514
4515 if (emp->inuse)
4516 {
4517 float yaw;
4518 vec3_t viewAngCap;
4519 int override;
4520
4521 VectorCopy(ent->client->ps.viewangles, viewAngCap);
4522 if (viewAngCap[PITCH] > 40)
4523 {
4524 viewAngCap[PITCH] = 40;
4525 }
4526
4527 override = BG_EmplacedView(ent->client->ps.viewangles, emp->s.angles, &yaw,
4528 emp->s.origin2[0]);
4529
4530 if (override)
4531 {
4532 viewAngCap[YAW] = yaw;
4533 }
4534
4535 AngleVectors( viewAngCap, forward, vright, up );
4536 }
4537 else
4538 {
4539 AngleVectors( ent->client->ps.viewangles, forward, vright, up );
4540 }
4541 }
4542 else if (ent->s.number < MAX_CLIENTS &&
4543 ent->client->ps.m_iVehicleNum && ent->s.weapon == WP_BLASTER)
4544 { //riding a vehicle...with blaster selected
4545 vec3_t vehTurnAngles;
4546 gentity_t *vehEnt = &g_entities[ent->client->ps.m_iVehicleNum];
4547
4548 if (vehEnt->inuse && vehEnt->client && vehEnt->m_pVehicle)
4549 {
4550 VectorCopy(vehEnt->m_pVehicle->m_vOrientation, vehTurnAngles);
4551 vehTurnAngles[PITCH] = ent->client->ps.viewangles[PITCH];
4552 }
4553 else
4554 {
4555 VectorCopy(ent->client->ps.viewangles, vehTurnAngles);
4556 }
4557 if (ent->client->pers.cmd.rightmove > 0)
4558 { //shooting to right
4559 vehTurnAngles[YAW] -= 90.0f;
4560 }
4561 else if (ent->client->pers.cmd.rightmove < 0)
4562 { //shooting to left
4563 vehTurnAngles[YAW] += 90.0f;
4564 }
4565
4566 AngleVectors( vehTurnAngles, forward, vright, up );
4567 }
4568 else
4569 {
4570 AngleVectors( ent->client->ps.viewangles, forward, vright, up );
4571 }
4572
4573 CalcMuzzlePoint ( ent, forward, vright, up, muzzle );
4574
4575 // fire the specific weapon
4576 switch( ent->s.weapon ) {
4577 case WP_STUN_BATON:
4578 WP_FireStunBaton( ent, altFire );
4579 break;
4580
4581 case WP_MELEE:
4582 WP_FireMelee(ent, altFire);
4583 break;
4584
4585 case WP_SABER:
4586 break;
4587
4588 case WP_BRYAR_PISTOL:
4589 WP_FireBryarPistol( ent, altFire );
4590 break;
4591
4592 case WP_CONCUSSION:
4593 if ( altFire )
4594 WP_FireConcussionAlt( ent );
4595 else
4596 WP_FireConcussion( ent );
4597 break;
4598
4599 case WP_BRYAR_OLD:
4600 WP_FireBryarPistol( ent, altFire );
4601 break;
4602
4603 case WP_BLASTER:
4604 WP_FireBlaster( ent, altFire );
4605 break;
4606
4607 case WP_DISRUPTOR:
4608 WP_FireDisruptor( ent, altFire );
4609 break;
4610
4611 case WP_BOWCASTER:
4612 WP_FireBowcaster( ent, altFire );
4613 break;
4614
4615 case WP_REPEATER:
4616 WP_FireRepeater( ent, altFire );
4617 break;
4618
4619 case WP_DEMP2:
4620 WP_FireDEMP2( ent, altFire );
4621 break;
4622
4623 case WP_FLECHETTE:
4624 WP_FireFlechette( ent, altFire );
4625 break;
4626
4627 case WP_ROCKET_LAUNCHER:
4628 WP_FireRocket( ent, altFire );
4629 break;
4630
4631 case WP_THERMAL:
4632 WP_FireThermalDetonator( ent, altFire );
4633 break;
4634
4635 case WP_TRIP_MINE:
4636 WP_PlaceLaserTrap( ent, altFire );
4637 break;
4638
4639 case WP_DET_PACK:
4640 WP_DropDetPack( ent, altFire );
4641 break;
4642
4643 case WP_EMPLACED_GUN:
4644 if (ent->client && ent->client->ewebIndex)
4645 { //specially handled by the e-web itself
4646 break;
4647 }
4648 WP_FireEmplaced( ent, altFire );
4649 break;
4650 default:
4651 // assert(!"unknown weapon fire");
4652 break;
4653 }
4654 }
4655
4656 G_LogWeaponFire(ent->s.number, ent->s.weapon);
4657 }
4658
4659 //---------------------------------------------------------
WP_FireEmplaced(gentity_t * ent,qboolean altFire)4660 static void WP_FireEmplaced( gentity_t *ent, qboolean altFire )
4661 //---------------------------------------------------------
4662 {
4663 vec3_t dir, angs, gunpoint;
4664 vec3_t right;
4665 gentity_t *gun;
4666 int side;
4667
4668 if (!ent->client)
4669 {
4670 return;
4671 }
4672
4673 if (!ent->client->ps.emplacedIndex)
4674 { //shouldn't be using WP_EMPLACED_GUN if we aren't on an emplaced weapon
4675 return;
4676 }
4677
4678 gun = &g_entities[ent->client->ps.emplacedIndex];
4679
4680 if (!gun->inuse || gun->health <= 0)
4681 { //gun was removed or killed, although we should never hit this check because we should have been forced off it already
4682 return;
4683 }
4684
4685 VectorCopy(gun->s.origin, gunpoint);
4686 gunpoint[2] += 46;
4687
4688 AngleVectors(ent->client->ps.viewangles, NULL, right, NULL);
4689
4690 if (gun->genericValue10)
4691 { //fire out of the right cannon side
4692 VectorMA(gunpoint, 10.0f, right, gunpoint);
4693 side = 0;
4694 }
4695 else
4696 { //the left
4697 VectorMA(gunpoint, -10.0f, right, gunpoint);
4698 side = 1;
4699 }
4700
4701 gun->genericValue10 = side;
4702 G_AddEvent(gun, EV_FIRE_WEAPON, side);
4703
4704 vectoangles( forward, angs );
4705
4706 AngleVectors( angs, dir, NULL, NULL );
4707
4708 WP_FireEmplacedMissile( gun, gunpoint, dir, altFire, ent );
4709 }
4710
4711 #define EMPLACED_CANRESPAWN 1
4712
4713 //----------------------------------------------------------
4714
4715 /*QUAKED emplaced_gun (0 0 1) (-30 -20 8) (30 20 60) CANRESPAWN
4716
4717 count - if CANRESPAWN spawnflag, decides how long it is before gun respawns (in ms)
4718 constraint - number of degrees gun is constrained from base angles on each side (default 60.0)
4719
4720 showhealth - set to 1 to show health bar on this entity when crosshair is over it
4721
4722 teamowner - crosshair shows green for this team, red for opposite team
4723 0 - none
4724 1 - red
4725 2 - blue
4726
4727 alliedTeam - team that can use this
4728 0 - any
4729 1 - red
4730 2 - blue
4731
4732 teamnodmg - team that turret does not take damage from or do damage to
4733 0 - none
4734 1 - red
4735 2 - blue
4736 */
4737
4738 //----------------------------------------------------------
4739 extern qboolean TryHeal(gentity_t *ent, gentity_t *target); //g_utils.c
emplaced_gun_use(gentity_t * self,gentity_t * other,trace_t * trace)4740 void emplaced_gun_use( gentity_t *self, gentity_t *other, trace_t *trace )
4741 {
4742 vec3_t fwd1, fwd2;
4743 float dot;
4744 int oldWeapon;
4745 gentity_t *activator = other;
4746 float zoffset = 50;
4747 vec3_t anglesToOwner;
4748 vec3_t vLen;
4749 float ownLen;
4750
4751 if ( self->health <= 0 )
4752 { //gun is destroyed
4753 return;
4754 }
4755
4756 if (self->activator)
4757 { //someone is already using me
4758 return;
4759 }
4760
4761 if (!activator->client)
4762 {
4763 return;
4764 }
4765
4766 if (activator->client->ps.emplacedTime > level.time)
4767 { //last use attempt still too recent
4768 return;
4769 }
4770
4771 if (activator->client->ps.forceHandExtend != HANDEXTEND_NONE)
4772 { //don't use if busy doing something else
4773 return;
4774 }
4775
4776 if (activator->client->ps.origin[2] > self->s.origin[2]+zoffset-8)
4777 { //can't use it from the top
4778 return;
4779 }
4780
4781 if (activator->client->ps.pm_flags & PMF_DUCKED)
4782 { //must be standing
4783 return;
4784 }
4785
4786 if (activator->client->ps.isJediMaster)
4787 { //jm can't use weapons
4788 return;
4789 }
4790
4791 VectorSubtract(self->s.origin, activator->client->ps.origin, vLen);
4792 ownLen = VectorLength(vLen);
4793
4794 if (ownLen > 64.0f)
4795 { //must be within 64 units of the gun to use at all
4796 return;
4797 }
4798
4799 // Let's get some direction vectors for the user
4800 AngleVectors( activator->client->ps.viewangles, fwd1, NULL, NULL );
4801
4802 // Get the guns direction vector
4803 AngleVectors( self->pos1, fwd2, NULL, NULL );
4804
4805 dot = DotProduct( fwd1, fwd2 );
4806
4807 // Must be reasonably facing the way the gun points ( 110 degrees or so ), otherwise we don't allow to use it.
4808 if ( dot < -0.2f )
4809 {
4810 goto tryHeal;
4811 }
4812
4813 VectorSubtract(self->s.origin, activator->client->ps.origin, fwd1);
4814 VectorNormalize(fwd1);
4815
4816 dot = DotProduct( fwd1, fwd2 );
4817
4818 //check the positioning in relation to the gun as well
4819 if ( dot < 0.6f )
4820 {
4821 goto tryHeal;
4822 }
4823
4824 self->genericValue1 = 1;
4825
4826 oldWeapon = activator->s.weapon;
4827
4828 // swap the users weapon with the emplaced gun
4829 activator->client->ps.weapon = self->s.weapon;
4830 activator->client->ps.weaponstate = WEAPON_READY;
4831 activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN );
4832
4833 activator->client->ps.emplacedIndex = self->s.number;
4834
4835 self->s.emplacedOwner = activator->s.number;
4836 self->s.activeForcePass = NUM_FORCE_POWERS+1;
4837
4838 // the gun will track which weapon we used to have
4839 self->s.weapon = oldWeapon;
4840
4841 //user's new owner becomes the gun ent
4842 activator->r.ownerNum = self->s.number;
4843 self->activator = activator;
4844
4845 VectorSubtract(self->r.currentOrigin, activator->client->ps.origin, anglesToOwner);
4846 vectoangles(anglesToOwner, anglesToOwner);
4847 return;
4848
4849 tryHeal: //well, not in the right dir, try healing it instead...
4850 TryHeal(activator, self);
4851 }
4852
emplaced_gun_realuse(gentity_t * self,gentity_t * other,gentity_t * activator)4853 void emplaced_gun_realuse( gentity_t *self, gentity_t *other, gentity_t *activator )
4854 {
4855 emplaced_gun_use(self, other, NULL);
4856 }
4857
4858 //----------------------------------------------------------
emplaced_gun_pain(gentity_t * self,gentity_t * attacker,int damage)4859 void emplaced_gun_pain( gentity_t *self, gentity_t *attacker, int damage )
4860 {
4861 self->s.health = self->health;
4862
4863 if ( self->health <= 0 )
4864 {
4865 //death effect.. for now taken care of on cgame
4866 }
4867 else
4868 {
4869 //if we have a pain behavior set then use it I guess
4870 G_ActivateBehavior( self, BSET_PAIN );
4871 }
4872 }
4873
4874 #define EMPLACED_GUN_HEALTH 800
4875
4876 //----------------------------------------------------------
emplaced_gun_update(gentity_t * self)4877 void emplaced_gun_update(gentity_t *self)
4878 {
4879 vec3_t smokeOrg, puffAngle;
4880 int oldWeap;
4881 float ownLen = 0;
4882
4883 if (self->health < 1 && !self->genericValue5)
4884 { //we are dead, set our respawn delay if we have one
4885 if (self->spawnflags & EMPLACED_CANRESPAWN)
4886 {
4887 self->genericValue5 = level.time + 4000 + self->count;
4888 }
4889 }
4890 else if (self->health < 1 && self->genericValue5 < level.time)
4891 { //we are dead, see if it's time to respawn
4892 self->s.time = 0;
4893 self->genericValue4 = 0;
4894 self->genericValue3 = 0;
4895 self->health = EMPLACED_GUN_HEALTH*0.4;
4896 self->s.health = self->health;
4897 }
4898
4899 if (self->genericValue4 && self->genericValue4 < 2 && self->s.time < level.time)
4900 { //we have finished our warning (red flashing) effect, it's time to finish dying
4901 vec3_t explOrg;
4902
4903 VectorSet( puffAngle, 0, 0, 1 );
4904
4905 VectorCopy(self->r.currentOrigin, explOrg);
4906 explOrg[2] += 16;
4907
4908 //just use the detpack explosion effect
4909 G_PlayEffect(EFFECT_EXPLOSION_DETPACK, explOrg, puffAngle);
4910
4911 self->genericValue3 = level.time + Q_irand(2500, 3500);
4912
4913 G_RadiusDamage(self->r.currentOrigin, self, self->splashDamage, self->splashRadius, self, NULL, MOD_UNKNOWN);
4914
4915 self->s.time = -1;
4916
4917 self->genericValue4 = 2;
4918 }
4919
4920 if (self->genericValue3 > level.time)
4921 { //see if we are freshly dead and should be smoking
4922 if (self->genericValue2 < level.time)
4923 { //is it time yet to spawn another smoke puff?
4924 VectorSet( puffAngle, 0, 0, 1 );
4925 VectorCopy(self->r.currentOrigin, smokeOrg);
4926
4927 smokeOrg[2] += 60;
4928
4929 G_PlayEffect(EFFECT_SMOKE, smokeOrg, puffAngle);
4930 self->genericValue2 = level.time + Q_irand(250, 400);
4931 }
4932 }
4933
4934 if (self->activator && self->activator->client && self->activator->inuse)
4935 { //handle updating current user
4936 vec3_t vLen;
4937 VectorSubtract(self->s.origin, self->activator->client->ps.origin, vLen);
4938 ownLen = VectorLength(vLen);
4939
4940 if (!(self->activator->client->pers.cmd.buttons & BUTTON_USE) && self->genericValue1)
4941 {
4942 self->genericValue1 = 0;
4943 }
4944
4945 if ((self->activator->client->pers.cmd.buttons & BUTTON_USE) && !self->genericValue1)
4946 {
4947 self->activator->client->ps.emplacedIndex = 0;
4948 self->activator->client->ps.saberHolstered = 0;
4949 self->nextthink = level.time + 50;
4950 return;
4951 }
4952 }
4953
4954 if ((self->activator && self->activator->client) &&
4955 (!self->activator->inuse || self->activator->client->ps.emplacedIndex != self->s.number || self->genericValue4 || ownLen > 64))
4956 { //get the user off of me then
4957 self->activator->client->ps.stats[STAT_WEAPONS] &= ~(1<<WP_EMPLACED_GUN);
4958
4959 oldWeap = self->activator->client->ps.weapon;
4960 self->activator->client->ps.weapon = self->s.weapon;
4961 self->s.weapon = oldWeap;
4962 self->activator->r.ownerNum = ENTITYNUM_NONE;
4963 self->activator->client->ps.emplacedTime = level.time + 1000;
4964 self->activator->client->ps.emplacedIndex = 0;
4965 self->activator->client->ps.saberHolstered = 0;
4966 self->activator = NULL;
4967
4968 self->s.activeForcePass = 0;
4969 }
4970 else if (self->activator && self->activator->client)
4971 { //make sure the user is using the emplaced gun weapon
4972 self->activator->client->ps.weapon = WP_EMPLACED_GUN;
4973 self->activator->client->ps.weaponstate = WEAPON_READY;
4974 }
4975 self->nextthink = level.time + 50;
4976 }
4977
4978 //----------------------------------------------------------
emplaced_gun_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)4979 void emplaced_gun_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
4980 { //set us up to flash and then explode
4981 if (self->genericValue4)
4982 {
4983 return;
4984 }
4985
4986 self->genericValue4 = 1;
4987
4988 self->s.time = level.time + 3000;
4989
4990 self->genericValue5 = 0;
4991 }
4992
SP_emplaced_gun(gentity_t * ent)4993 void SP_emplaced_gun( gentity_t *ent )
4994 {
4995 const char *name = "models/map_objects/mp/turret_chair.glm";
4996 vec3_t down;
4997 trace_t tr;
4998
4999 //make sure our assets are precached
5000 RegisterItem( BG_FindItemForWeapon(WP_EMPLACED_GUN) );
5001
5002 ent->r.contents = CONTENTS_SOLID;
5003 ent->s.solid = SOLID_BBOX;
5004
5005 ent->genericValue5 = 0;
5006
5007 VectorSet( ent->r.mins, -30, -20, 8 );
5008 VectorSet( ent->r.maxs, 30, 20, 60 );
5009
5010 VectorCopy(ent->s.origin, down);
5011
5012 down[2] -= 1024;
5013
5014 trap->Trace(&tr, ent->s.origin, ent->r.mins, ent->r.maxs, down, ent->s.number, MASK_SOLID, qfalse, 0, 0);
5015
5016 if (tr.fraction != 1 && !tr.allsolid && !tr.startsolid)
5017 {
5018 VectorCopy(tr.endpos, ent->s.origin);
5019 }
5020
5021 ent->spawnflags |= 4; // deadsolid
5022
5023 ent->health = EMPLACED_GUN_HEALTH;
5024
5025 if (ent->spawnflags & EMPLACED_CANRESPAWN)
5026 { //make it somewhat easier to kill if it can respawn
5027 ent->health *= 0.4;
5028 }
5029
5030 ent->maxHealth = ent->health;
5031 G_ScaleNetHealth(ent);
5032
5033 ent->genericValue4 = 0;
5034
5035 ent->takedamage = qtrue;
5036 ent->pain = emplaced_gun_pain;
5037 ent->die = emplaced_gun_die;
5038
5039 // being caught in this thing when it blows would be really bad.
5040 ent->splashDamage = 80;
5041 ent->splashRadius = 128;
5042
5043 // amount of ammo that this little poochie has
5044 G_SpawnInt( "count", "600", &ent->count );
5045
5046 G_SpawnFloat( "constraint", "60", &ent->s.origin2[0] );
5047
5048 ent->s.modelindex = G_ModelIndex( (char *)name );
5049 ent->s.modelGhoul2 = 1;
5050 ent->s.g2radius = 110;
5051
5052 //so the cgame knows for sure that we're an emplaced weapon
5053 ent->s.weapon = WP_EMPLACED_GUN;
5054
5055 G_SetOrigin( ent, ent->s.origin );
5056
5057 // store base angles for later
5058 VectorCopy( ent->s.angles, ent->pos1 );
5059 VectorCopy( ent->s.angles, ent->r.currentAngles );
5060 VectorCopy( ent->s.angles, ent->s.apos.trBase );
5061
5062 ent->think = emplaced_gun_update;
5063 ent->nextthink = level.time + 50;
5064
5065 ent->use = emplaced_gun_realuse;
5066
5067 ent->r.svFlags |= SVF_PLAYER_USABLE;
5068
5069 ent->s.pos.trType = TR_STATIONARY;
5070
5071 ent->s.owner = MAX_CLIENTS+1;
5072 ent->s.shouldtarget = qtrue;
5073 //ent->s.teamowner = 0;
5074
5075 trap->LinkEntity((sharedEntity_t *)ent);
5076 }
5077