1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22 //
23 // g_weapon.c
24 // perform the server side effects of a weapon firing
25
26 #include "g_local.h"
27
28 static float s_quadFactor;
29 static vec3_t forward, right, up;
30 static vec3_t muzzle;
31
32 #define NUM_NAILSHOTS 15
33
34 /*
35 ================
36 G_BounceProjectile
37 ================
38 */
G_BounceProjectile(vec3_t start,vec3_t impact,vec3_t dir,vec3_t endout)39 void G_BounceProjectile( vec3_t start, vec3_t impact, vec3_t dir, vec3_t endout ) {
40 vec3_t v, newv;
41 float dot;
42
43 VectorSubtract( impact, start, v );
44 dot = DotProduct( v, dir );
45 VectorMA( v, -2*dot, dir, newv );
46
47 VectorNormalize(newv);
48 VectorMA(impact, 8192, newv, endout);
49 }
50
51
52 /*
53 ======================================================================
54
55 GAUNTLET
56
57 ======================================================================
58 */
59
Weapon_Gauntlet(gentity_t * ent)60 void Weapon_Gauntlet( gentity_t *ent ) {
61
62 }
63
64 /*
65 ===============
66 CheckGauntletAttack
67 ===============
68 */
CheckGauntletAttack(gentity_t * ent)69 qboolean CheckGauntletAttack( gentity_t *ent ) {
70 trace_t tr;
71 vec3_t end;
72 gentity_t *tent;
73 gentity_t *traceEnt;
74 int damage;
75
76 // set aiming directions
77 AngleVectors (ent->client->ps.viewangles, forward, right, up);
78
79 CalcMuzzlePoint ( ent, forward, right, up, muzzle );
80
81 VectorMA (muzzle, 32, forward, end);
82
83 trap_Trace (&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT);
84 if ( tr.surfaceFlags & SURF_NOIMPACT ) {
85 return qfalse;
86 }
87
88 traceEnt = &g_entities[ tr.entityNum ];
89
90 // send blood impact
91 if ( traceEnt->takedamage && traceEnt->client ) {
92 tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
93 tent->s.otherEntityNum = traceEnt->s.number;
94 tent->s.eventParm = DirToByte( tr.plane.normal );
95 tent->s.weapon = ent->s.weapon;
96 }
97
98 if ( !traceEnt->takedamage) {
99 return qfalse;
100 }
101
102 if (ent->client->ps.powerups[PW_QUAD] ) {
103 G_AddEvent( ent, EV_POWERUP_QUAD, 0 );
104 s_quadFactor = g_quadfactor.value;
105 } else {
106 s_quadFactor = 1;
107 }
108 #ifdef MISSIONPACK
109 if( ent->client->persistantPowerup && ent->client->persistantPowerup->item && ent->client->persistantPowerup->item->giTag == PW_DOUBLER ) {
110 s_quadFactor *= 2;
111 }
112 #endif
113
114 damage = 50 * s_quadFactor;
115 G_Damage( traceEnt, ent, ent, forward, tr.endpos,
116 damage, 0, MOD_GAUNTLET );
117
118 return qtrue;
119 }
120
121
122 /*
123 ======================================================================
124
125 MACHINEGUN
126
127 ======================================================================
128 */
129
130 /*
131 ======================
132 SnapVectorTowards
133
134 Round a vector to integers for more efficient network
135 transmission, but make sure that it rounds towards a given point
136 rather than blindly truncating. This prevents it from truncating
137 into a wall.
138 ======================
139 */
SnapVectorTowards(vec3_t v,vec3_t to)140 void SnapVectorTowards( vec3_t v, vec3_t to ) {
141 int i;
142
143 for ( i = 0 ; i < 3 ; i++ ) {
144 if ( to[i] <= v[i] ) {
145 v[i] = (int)v[i];
146 } else {
147 v[i] = (int)v[i] + 1;
148 }
149 }
150 }
151
152 #ifdef MISSIONPACK
153 #define CHAINGUN_SPREAD 600
154 #endif
155 #define MACHINEGUN_SPREAD 200
156 #define MACHINEGUN_DAMAGE 7
157 #define MACHINEGUN_TEAM_DAMAGE 5 // wimpier MG in teamplay
158
Bullet_Fire(gentity_t * ent,float spread,int damage)159 void Bullet_Fire (gentity_t *ent, float spread, int damage ) {
160 trace_t tr;
161 vec3_t end;
162 #ifdef MISSIONPACK
163 vec3_t impactpoint, bouncedir;
164 #endif
165 float r;
166 float u;
167 gentity_t *tent;
168 gentity_t *traceEnt;
169 int i, passent;
170
171 damage *= s_quadFactor;
172
173 r = random() * M_PI * 2.0f;
174 u = sin(r) * crandom() * spread * 16;
175 r = cos(r) * crandom() * spread * 16;
176 VectorMA (muzzle, 8192*16, forward, end);
177 VectorMA (end, r, right, end);
178 VectorMA (end, u, up, end);
179
180 passent = ent->s.number;
181 for (i = 0; i < 10; i++) {
182
183 trap_Trace (&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT);
184 if ( tr.surfaceFlags & SURF_NOIMPACT ) {
185 return;
186 }
187
188 traceEnt = &g_entities[ tr.entityNum ];
189
190 // snap the endpos to integers, but nudged towards the line
191 SnapVectorTowards( tr.endpos, muzzle );
192
193 // send bullet impact
194 if ( traceEnt->takedamage && traceEnt->client ) {
195 tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH );
196 tent->s.eventParm = traceEnt->s.number;
197 if( LogAccuracyHit( traceEnt, ent ) ) {
198 ent->client->accuracy_hits++;
199 }
200 } else {
201 tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL );
202 tent->s.eventParm = DirToByte( tr.plane.normal );
203 }
204 tent->s.otherEntityNum = ent->s.number;
205
206 if ( traceEnt->takedamage) {
207 #ifdef MISSIONPACK
208 if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) {
209 if (G_InvulnerabilityEffect( traceEnt, forward, tr.endpos, impactpoint, bouncedir )) {
210 G_BounceProjectile( muzzle, impactpoint, bouncedir, end );
211 VectorCopy( impactpoint, muzzle );
212 // the player can hit him/herself with the bounced rail
213 passent = ENTITYNUM_NONE;
214 }
215 else {
216 VectorCopy( tr.endpos, muzzle );
217 passent = traceEnt->s.number;
218 }
219 continue;
220 }
221 else {
222 #endif
223 G_Damage( traceEnt, ent, ent, forward, tr.endpos,
224 damage, 0, MOD_MACHINEGUN);
225 #ifdef MISSIONPACK
226 }
227 #endif
228 }
229 break;
230 }
231 }
232
233
234 /*
235 ======================================================================
236
237 BFG
238
239 ======================================================================
240 */
241
BFG_Fire(gentity_t * ent)242 void BFG_Fire ( gentity_t *ent ) {
243 gentity_t *m;
244
245 m = fire_bfg (ent, muzzle, forward);
246 m->damage *= s_quadFactor;
247 m->splashDamage *= s_quadFactor;
248
249 // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
250 }
251
252
253 /*
254 ======================================================================
255
256 SHOTGUN
257
258 ======================================================================
259 */
260
261 // DEFAULT_SHOTGUN_SPREAD and DEFAULT_SHOTGUN_COUNT are in bg_public.h, because
262 // client predicts same spreads
263 #define DEFAULT_SHOTGUN_DAMAGE 10
264
ShotgunPellet(vec3_t start,vec3_t end,gentity_t * ent)265 qboolean ShotgunPellet( vec3_t start, vec3_t end, gentity_t *ent ) {
266 trace_t tr;
267 int damage, i, passent;
268 gentity_t *traceEnt;
269 #ifdef MISSIONPACK
270 vec3_t impactpoint, bouncedir;
271 #endif
272 vec3_t tr_start, tr_end;
273
274 passent = ent->s.number;
275 VectorCopy( start, tr_start );
276 VectorCopy( end, tr_end );
277 for (i = 0; i < 10; i++) {
278 trap_Trace (&tr, tr_start, NULL, NULL, tr_end, passent, MASK_SHOT);
279 traceEnt = &g_entities[ tr.entityNum ];
280
281 // send bullet impact
282 if ( tr.surfaceFlags & SURF_NOIMPACT ) {
283 return qfalse;
284 }
285
286 if ( traceEnt->takedamage) {
287 damage = DEFAULT_SHOTGUN_DAMAGE * s_quadFactor;
288 #ifdef MISSIONPACK
289 if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) {
290 if (G_InvulnerabilityEffect( traceEnt, forward, tr.endpos, impactpoint, bouncedir )) {
291 G_BounceProjectile( tr_start, impactpoint, bouncedir, tr_end );
292 VectorCopy( impactpoint, tr_start );
293 // the player can hit him/herself with the bounced rail
294 passent = ENTITYNUM_NONE;
295 }
296 else {
297 VectorCopy( tr.endpos, tr_start );
298 passent = traceEnt->s.number;
299 }
300 continue;
301 }
302 else {
303 G_Damage( traceEnt, ent, ent, forward, tr.endpos,
304 damage, 0, MOD_SHOTGUN);
305 if( LogAccuracyHit( traceEnt, ent ) ) {
306 return qtrue;
307 }
308 }
309 #else
310 G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_SHOTGUN);
311 if( LogAccuracyHit( traceEnt, ent ) ) {
312 return qtrue;
313 }
314 #endif
315 }
316 return qfalse;
317 }
318 return qfalse;
319 }
320
321 // this should match CG_ShotgunPattern
ShotgunPattern(vec3_t origin,vec3_t origin2,int seed,gentity_t * ent)322 void ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent ) {
323 int i;
324 float r, u;
325 vec3_t end;
326 vec3_t forward, right, up;
327 int oldScore;
328 qboolean hitClient = qfalse;
329
330 // derive the right and up vectors from the forward vector, because
331 // the client won't have any other information
332 VectorNormalize2( origin2, forward );
333 PerpendicularVector( right, forward );
334 CrossProduct( forward, right, up );
335
336 oldScore = ent->client->ps.persistant[PERS_SCORE];
337
338 // generate the "random" spread pattern
339 for ( i = 0 ; i < DEFAULT_SHOTGUN_COUNT ; i++ ) {
340 r = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16;
341 u = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16;
342 VectorMA( origin, 8192 * 16, forward, end);
343 VectorMA (end, r, right, end);
344 VectorMA (end, u, up, end);
345 if( ShotgunPellet( origin, end, ent ) && !hitClient ) {
346 hitClient = qtrue;
347 ent->client->accuracy_hits++;
348 }
349 }
350 }
351
352
weapon_supershotgun_fire(gentity_t * ent)353 void weapon_supershotgun_fire (gentity_t *ent) {
354 gentity_t *tent;
355
356 // send shotgun blast
357 tent = G_TempEntity( muzzle, EV_SHOTGUN );
358 VectorScale( forward, 4096, tent->s.origin2 );
359 SnapVector( tent->s.origin2 );
360 tent->s.eventParm = rand() & 255; // seed for spread pattern
361 tent->s.otherEntityNum = ent->s.number;
362
363 ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent );
364 }
365
366
367 /*
368 ======================================================================
369
370 GRENADE LAUNCHER
371
372 ======================================================================
373 */
374
weapon_grenadelauncher_fire(gentity_t * ent)375 void weapon_grenadelauncher_fire (gentity_t *ent) {
376 gentity_t *m;
377
378 // extra vertical velocity
379 forward[2] += 0.2f;
380 VectorNormalize( forward );
381
382 m = fire_grenade (ent, muzzle, forward);
383 m->damage *= s_quadFactor;
384 m->splashDamage *= s_quadFactor;
385
386 // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
387 }
388
389 /*
390 ======================================================================
391
392 ROCKET
393
394 ======================================================================
395 */
396
Weapon_RocketLauncher_Fire(gentity_t * ent)397 void Weapon_RocketLauncher_Fire (gentity_t *ent) {
398 gentity_t *m;
399
400 m = fire_rocket (ent, muzzle, forward);
401 m->damage *= s_quadFactor;
402 m->splashDamage *= s_quadFactor;
403
404 // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
405 }
406
407
408 /*
409 ======================================================================
410
411 PLASMA GUN
412
413 ======================================================================
414 */
415
Weapon_Plasmagun_Fire(gentity_t * ent)416 void Weapon_Plasmagun_Fire (gentity_t *ent) {
417 gentity_t *m;
418
419 m = fire_plasma (ent, muzzle, forward);
420 m->damage *= s_quadFactor;
421 m->splashDamage *= s_quadFactor;
422
423 // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
424 }
425
426 /*
427 ======================================================================
428
429 RAILGUN
430
431 ======================================================================
432 */
433
434
435 /*
436 =================
437 weapon_railgun_fire
438 =================
439 */
440 #define MAX_RAIL_HITS 4
weapon_railgun_fire(gentity_t * ent)441 void weapon_railgun_fire (gentity_t *ent) {
442 vec3_t end;
443 #ifdef MISSIONPACK
444 vec3_t impactpoint, bouncedir;
445 #endif
446 trace_t trace;
447 gentity_t *tent;
448 gentity_t *traceEnt;
449 int damage;
450 int i;
451 int hits;
452 int unlinked;
453 int passent;
454 gentity_t *unlinkedEntities[MAX_RAIL_HITS];
455
456 damage = 100 * s_quadFactor;
457
458 VectorMA (muzzle, 8192, forward, end);
459
460 // trace only against the solids, so the railgun will go through people
461 unlinked = 0;
462 hits = 0;
463 passent = ent->s.number;
464 do {
465 trap_Trace (&trace, muzzle, NULL, NULL, end, passent, MASK_SHOT );
466 if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) {
467 break;
468 }
469 traceEnt = &g_entities[ trace.entityNum ];
470 if ( traceEnt->takedamage ) {
471 #ifdef MISSIONPACK
472 if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) {
473 if ( G_InvulnerabilityEffect( traceEnt, forward, trace.endpos, impactpoint, bouncedir ) ) {
474 G_BounceProjectile( muzzle, impactpoint, bouncedir, end );
475 // snap the endpos to integers to save net bandwidth, but nudged towards the line
476 SnapVectorTowards( trace.endpos, muzzle );
477 // send railgun beam effect
478 tent = G_TempEntity( trace.endpos, EV_RAILTRAIL );
479 // set player number for custom colors on the railtrail
480 tent->s.clientNum = ent->s.clientNum;
481 VectorCopy( muzzle, tent->s.origin2 );
482 // move origin a bit to come closer to the drawn gun muzzle
483 VectorMA( tent->s.origin2, 4, right, tent->s.origin2 );
484 VectorMA( tent->s.origin2, -1, up, tent->s.origin2 );
485 tent->s.eventParm = 255; // don't make the explosion at the end
486 //
487 VectorCopy( impactpoint, muzzle );
488 // the player can hit him/herself with the bounced rail
489 passent = ENTITYNUM_NONE;
490 }
491 }
492 else {
493 if( LogAccuracyHit( traceEnt, ent ) ) {
494 hits++;
495 }
496 G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN);
497 }
498 #else
499 if( LogAccuracyHit( traceEnt, ent ) ) {
500 hits++;
501 }
502 G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN);
503 #endif
504 }
505 if ( trace.contents & CONTENTS_SOLID ) {
506 break; // we hit something solid enough to stop the beam
507 }
508 // unlink this entity, so the next trace will go past it
509 trap_UnlinkEntity( traceEnt );
510 unlinkedEntities[unlinked] = traceEnt;
511 unlinked++;
512 } while ( unlinked < MAX_RAIL_HITS );
513
514 // link back in any entities we unlinked
515 for ( i = 0 ; i < unlinked ; i++ ) {
516 trap_LinkEntity( unlinkedEntities[i] );
517 }
518
519 // the final trace endpos will be the terminal point of the rail trail
520
521 // snap the endpos to integers to save net bandwidth, but nudged towards the line
522 SnapVectorTowards( trace.endpos, muzzle );
523
524 // send railgun beam effect
525 tent = G_TempEntity( trace.endpos, EV_RAILTRAIL );
526
527 // set player number for custom colors on the railtrail
528 tent->s.clientNum = ent->s.clientNum;
529
530 VectorCopy( muzzle, tent->s.origin2 );
531 // move origin a bit to come closer to the drawn gun muzzle
532 VectorMA( tent->s.origin2, 4, right, tent->s.origin2 );
533 VectorMA( tent->s.origin2, -1, up, tent->s.origin2 );
534
535 // no explosion at end if SURF_NOIMPACT, but still make the trail
536 if ( trace.surfaceFlags & SURF_NOIMPACT ) {
537 tent->s.eventParm = 255; // don't make the explosion at the end
538 } else {
539 tent->s.eventParm = DirToByte( trace.plane.normal );
540 }
541 tent->s.clientNum = ent->s.clientNum;
542
543 // give the shooter a reward sound if they have made two railgun hits in a row
544 if ( hits == 0 ) {
545 // complete miss
546 ent->client->accurateCount = 0;
547 } else {
548 // check for "impressive" reward sound
549 ent->client->accurateCount += hits;
550 if ( ent->client->accurateCount >= 2 ) {
551 ent->client->accurateCount -= 2;
552 ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++;
553 // add the sprite over the player's head
554 ent->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
555 ent->client->ps.eFlags |= EF_AWARD_IMPRESSIVE;
556 ent->client->rewardTime = level.time + REWARD_SPRITE_TIME;
557 }
558 ent->client->accuracy_hits++;
559 }
560
561 }
562
563
564 /*
565 ======================================================================
566
567 GRAPPLING HOOK
568
569 ======================================================================
570 */
571
Weapon_GrapplingHook_Fire(gentity_t * ent)572 void Weapon_GrapplingHook_Fire (gentity_t *ent)
573 {
574 if (!ent->client->fireHeld && !ent->client->hook)
575 fire_grapple (ent, muzzle, forward);
576
577 ent->client->fireHeld = qtrue;
578 }
579
Weapon_HookFree(gentity_t * ent)580 void Weapon_HookFree (gentity_t *ent)
581 {
582 ent->parent->client->hook = NULL;
583 ent->parent->client->ps.pm_flags &= ~PMF_GRAPPLE_PULL;
584 G_FreeEntity( ent );
585 }
586
Weapon_HookThink(gentity_t * ent)587 void Weapon_HookThink (gentity_t *ent)
588 {
589 if (ent->enemy) {
590 vec3_t v, oldorigin;
591
592 VectorCopy(ent->r.currentOrigin, oldorigin);
593 v[0] = ent->enemy->r.currentOrigin[0] + (ent->enemy->r.mins[0] + ent->enemy->r.maxs[0]) * 0.5;
594 v[1] = ent->enemy->r.currentOrigin[1] + (ent->enemy->r.mins[1] + ent->enemy->r.maxs[1]) * 0.5;
595 v[2] = ent->enemy->r.currentOrigin[2] + (ent->enemy->r.mins[2] + ent->enemy->r.maxs[2]) * 0.5;
596 SnapVectorTowards( v, oldorigin ); // save net bandwidth
597
598 G_SetOrigin( ent, v );
599 }
600
601 VectorCopy( ent->r.currentOrigin, ent->parent->client->ps.grapplePoint);
602 }
603
604 /*
605 ======================================================================
606
607 LIGHTNING GUN
608
609 ======================================================================
610 */
611
Weapon_LightningFire(gentity_t * ent)612 void Weapon_LightningFire( gentity_t *ent ) {
613 trace_t tr;
614 vec3_t end;
615 #ifdef MISSIONPACK
616 vec3_t impactpoint, bouncedir;
617 #endif
618 gentity_t *traceEnt, *tent;
619 int damage, i, passent;
620
621 damage = 8 * s_quadFactor;
622
623 passent = ent->s.number;
624 for (i = 0; i < 10; i++) {
625 VectorMA( muzzle, LIGHTNING_RANGE, forward, end );
626
627 trap_Trace( &tr, muzzle, NULL, NULL, end, passent, MASK_SHOT );
628
629 #ifdef MISSIONPACK
630 // if not the first trace (the lightning bounced of an invulnerability sphere)
631 if (i) {
632 // add bounced off lightning bolt temp entity
633 // the first lightning bolt is a cgame only visual
634 //
635 tent = G_TempEntity( muzzle, EV_LIGHTNINGBOLT );
636 VectorCopy( tr.endpos, end );
637 SnapVector( end );
638 VectorCopy( end, tent->s.origin2 );
639 }
640 #endif
641 if ( tr.entityNum == ENTITYNUM_NONE ) {
642 return;
643 }
644
645 traceEnt = &g_entities[ tr.entityNum ];
646
647 if ( traceEnt->takedamage) {
648 #ifdef MISSIONPACK
649 if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) {
650 if (G_InvulnerabilityEffect( traceEnt, forward, tr.endpos, impactpoint, bouncedir )) {
651 G_BounceProjectile( muzzle, impactpoint, bouncedir, end );
652 VectorCopy( impactpoint, muzzle );
653 VectorSubtract( end, impactpoint, forward );
654 VectorNormalize(forward);
655 // the player can hit him/herself with the bounced lightning
656 passent = ENTITYNUM_NONE;
657 }
658 else {
659 VectorCopy( tr.endpos, muzzle );
660 passent = traceEnt->s.number;
661 }
662 continue;
663 }
664 else {
665 G_Damage( traceEnt, ent, ent, forward, tr.endpos,
666 damage, 0, MOD_LIGHTNING);
667 }
668 #else
669 G_Damage( traceEnt, ent, ent, forward, tr.endpos,
670 damage, 0, MOD_LIGHTNING);
671 #endif
672 }
673
674 if ( traceEnt->takedamage && traceEnt->client ) {
675 tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
676 tent->s.otherEntityNum = traceEnt->s.number;
677 tent->s.eventParm = DirToByte( tr.plane.normal );
678 tent->s.weapon = ent->s.weapon;
679 if( LogAccuracyHit( traceEnt, ent ) ) {
680 ent->client->accuracy_hits++;
681 }
682 } else if ( !( tr.surfaceFlags & SURF_NOIMPACT ) ) {
683 tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
684 tent->s.eventParm = DirToByte( tr.plane.normal );
685 }
686
687 break;
688 }
689 }
690
691 #ifdef MISSIONPACK
692 /*
693 ======================================================================
694
695 NAILGUN
696
697 ======================================================================
698 */
699
Weapon_Nailgun_Fire(gentity_t * ent)700 void Weapon_Nailgun_Fire (gentity_t *ent) {
701 gentity_t *m;
702 int count;
703
704 for( count = 0; count < NUM_NAILSHOTS; count++ ) {
705 m = fire_nail (ent, muzzle, forward, right, up );
706 m->damage *= s_quadFactor;
707 m->splashDamage *= s_quadFactor;
708 }
709
710 // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
711 }
712
713
714 /*
715 ======================================================================
716
717 PROXIMITY MINE LAUNCHER
718
719 ======================================================================
720 */
721
weapon_proxlauncher_fire(gentity_t * ent)722 void weapon_proxlauncher_fire (gentity_t *ent) {
723 gentity_t *m;
724
725 // extra vertical velocity
726 forward[2] += 0.2f;
727 VectorNormalize( forward );
728
729 m = fire_prox (ent, muzzle, forward);
730 m->damage *= s_quadFactor;
731 m->splashDamage *= s_quadFactor;
732
733 // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
734 }
735
736 #endif
737
738 //======================================================================
739
740
741 /*
742 ===============
743 LogAccuracyHit
744 ===============
745 */
LogAccuracyHit(gentity_t * target,gentity_t * attacker)746 qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) {
747 if( !target->takedamage ) {
748 return qfalse;
749 }
750
751 if ( target == attacker ) {
752 return qfalse;
753 }
754
755 if( !target->client ) {
756 return qfalse;
757 }
758
759 if( !attacker->client ) {
760 return qfalse;
761 }
762
763 if( target->client->ps.stats[STAT_HEALTH] <= 0 ) {
764 return qfalse;
765 }
766
767 if ( OnSameTeam( target, attacker ) ) {
768 return qfalse;
769 }
770
771 return qtrue;
772 }
773
774
775 /*
776 ===============
777 CalcMuzzlePoint
778
779 set muzzle location relative to pivoting eye
780 ===============
781 */
CalcMuzzlePoint(gentity_t * ent,vec3_t forward,vec3_t right,vec3_t up,vec3_t muzzlePoint)782 void CalcMuzzlePoint ( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) {
783 VectorCopy( ent->s.pos.trBase, muzzlePoint );
784 muzzlePoint[2] += ent->client->ps.viewheight;
785 VectorMA( muzzlePoint, 14, forward, muzzlePoint );
786 // snap to integer coordinates for more efficient network bandwidth usage
787 SnapVector( muzzlePoint );
788 }
789
790 /*
791 ===============
792 CalcMuzzlePointOrigin
793
794 set muzzle location relative to pivoting eye
795 ===============
796 */
CalcMuzzlePointOrigin(gentity_t * ent,vec3_t origin,vec3_t forward,vec3_t right,vec3_t up,vec3_t muzzlePoint)797 void CalcMuzzlePointOrigin ( gentity_t *ent, vec3_t origin, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) {
798 VectorCopy( ent->s.pos.trBase, muzzlePoint );
799 muzzlePoint[2] += ent->client->ps.viewheight;
800 VectorMA( muzzlePoint, 14, forward, muzzlePoint );
801 // snap to integer coordinates for more efficient network bandwidth usage
802 SnapVector( muzzlePoint );
803 }
804
805
806
807 /*
808 ===============
809 FireWeapon
810 ===============
811 */
FireWeapon(gentity_t * ent)812 void FireWeapon( gentity_t *ent ) {
813 if (ent->client->ps.powerups[PW_QUAD] ) {
814 s_quadFactor = g_quadfactor.value;
815 } else {
816 s_quadFactor = 1;
817 }
818 #ifdef MISSIONPACK
819 if( ent->client->persistantPowerup && ent->client->persistantPowerup->item && ent->client->persistantPowerup->item->giTag == PW_DOUBLER ) {
820 s_quadFactor *= 2;
821 }
822 #endif
823
824 // track shots taken for accuracy tracking. Grapple is not a weapon and gauntet is just not tracked
825 if( ent->s.weapon != WP_GRAPPLING_HOOK && ent->s.weapon != WP_GAUNTLET ) {
826 #ifdef MISSIONPACK
827 if( ent->s.weapon == WP_NAILGUN ) {
828 ent->client->accuracy_shots += NUM_NAILSHOTS;
829 } else {
830 ent->client->accuracy_shots++;
831 }
832 #else
833 ent->client->accuracy_shots++;
834 #endif
835 }
836
837 // set aiming directions
838 AngleVectors (ent->client->ps.viewangles, forward, right, up);
839
840 CalcMuzzlePointOrigin ( ent, ent->client->oldOrigin, forward, right, up, muzzle );
841
842 // fire the specific weapon
843 switch( ent->s.weapon ) {
844 case WP_GAUNTLET:
845 Weapon_Gauntlet( ent );
846 break;
847 case WP_LIGHTNING:
848 Weapon_LightningFire( ent );
849 break;
850 case WP_SHOTGUN:
851 weapon_supershotgun_fire( ent );
852 break;
853 case WP_MACHINEGUN:
854 if ( g_gametype.integer != GT_TEAM ) {
855 Bullet_Fire( ent, MACHINEGUN_SPREAD, MACHINEGUN_DAMAGE );
856 } else {
857 Bullet_Fire( ent, MACHINEGUN_SPREAD, MACHINEGUN_TEAM_DAMAGE );
858 }
859 break;
860 case WP_GRENADE_LAUNCHER:
861 weapon_grenadelauncher_fire( ent );
862 break;
863 case WP_ROCKET_LAUNCHER:
864 Weapon_RocketLauncher_Fire( ent );
865 break;
866 case WP_PLASMAGUN:
867 Weapon_Plasmagun_Fire( ent );
868 break;
869 case WP_RAILGUN:
870 weapon_railgun_fire( ent );
871 break;
872 case WP_BFG:
873 BFG_Fire( ent );
874 break;
875 case WP_GRAPPLING_HOOK:
876 Weapon_GrapplingHook_Fire( ent );
877 break;
878 #ifdef MISSIONPACK
879 case WP_NAILGUN:
880 Weapon_Nailgun_Fire( ent );
881 break;
882 case WP_PROX_LAUNCHER:
883 weapon_proxlauncher_fire( ent );
884 break;
885 case WP_CHAINGUN:
886 Bullet_Fire( ent, CHAINGUN_SPREAD, MACHINEGUN_DAMAGE );
887 break;
888 #endif
889 default:
890 // FIXME G_Error( "Bad ent->s.weapon" );
891 break;
892 }
893 }
894
895
896 #ifdef MISSIONPACK
897
898 /*
899 ===============
900 KamikazeRadiusDamage
901 ===============
902 */
KamikazeRadiusDamage(vec3_t origin,gentity_t * attacker,float damage,float radius)903 static void KamikazeRadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius ) {
904 float dist;
905 gentity_t *ent;
906 int entityList[MAX_GENTITIES];
907 int numListedEntities;
908 vec3_t mins, maxs;
909 vec3_t v;
910 vec3_t dir;
911 int i, e;
912
913 if ( radius < 1 ) {
914 radius = 1;
915 }
916
917 for ( i = 0 ; i < 3 ; i++ ) {
918 mins[i] = origin[i] - radius;
919 maxs[i] = origin[i] + radius;
920 }
921
922 numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
923
924 for ( e = 0 ; e < numListedEntities ; e++ ) {
925 ent = &g_entities[entityList[ e ]];
926
927 if (!ent->takedamage) {
928 continue;
929 }
930
931 // dont hit things we have already hit
932 if( ent->kamikazeTime > level.time ) {
933 continue;
934 }
935
936 // find the distance from the edge of the bounding box
937 for ( i = 0 ; i < 3 ; i++ ) {
938 if ( origin[i] < ent->r.absmin[i] ) {
939 v[i] = ent->r.absmin[i] - origin[i];
940 } else if ( origin[i] > ent->r.absmax[i] ) {
941 v[i] = origin[i] - ent->r.absmax[i];
942 } else {
943 v[i] = 0;
944 }
945 }
946
947 dist = VectorLength( v );
948 if ( dist >= radius ) {
949 continue;
950 }
951
952 // if( CanDamage (ent, origin) ) {
953 VectorSubtract (ent->r.currentOrigin, origin, dir);
954 // push the center of mass higher than the origin so players
955 // get knocked into the air more
956 dir[2] += 24;
957 G_Damage( ent, NULL, attacker, dir, origin, damage, DAMAGE_RADIUS|DAMAGE_NO_TEAM_PROTECTION, MOD_KAMIKAZE );
958 ent->kamikazeTime = level.time + 3000;
959 // }
960 }
961 }
962
963 /*
964 ===============
965 KamikazeShockWave
966 ===============
967 */
KamikazeShockWave(vec3_t origin,gentity_t * attacker,float damage,float push,float radius)968 static void KamikazeShockWave( vec3_t origin, gentity_t *attacker, float damage, float push, float radius ) {
969 float dist;
970 gentity_t *ent;
971 int entityList[MAX_GENTITIES];
972 int numListedEntities;
973 vec3_t mins, maxs;
974 vec3_t v;
975 vec3_t dir;
976 int i, e;
977
978 if ( radius < 1 )
979 radius = 1;
980
981 for ( i = 0 ; i < 3 ; i++ ) {
982 mins[i] = origin[i] - radius;
983 maxs[i] = origin[i] + radius;
984 }
985
986 numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
987
988 for ( e = 0 ; e < numListedEntities ; e++ ) {
989 ent = &g_entities[entityList[ e ]];
990
991 // dont hit things we have already hit
992 if( ent->kamikazeShockTime > level.time ) {
993 continue;
994 }
995
996 // find the distance from the edge of the bounding box
997 for ( i = 0 ; i < 3 ; i++ ) {
998 if ( origin[i] < ent->r.absmin[i] ) {
999 v[i] = ent->r.absmin[i] - origin[i];
1000 } else if ( origin[i] > ent->r.absmax[i] ) {
1001 v[i] = origin[i] - ent->r.absmax[i];
1002 } else {
1003 v[i] = 0;
1004 }
1005 }
1006
1007 dist = VectorLength( v );
1008 if ( dist >= radius ) {
1009 continue;
1010 }
1011
1012 // if( CanDamage (ent, origin) ) {
1013 VectorSubtract (ent->r.currentOrigin, origin, dir);
1014 dir[2] += 24;
1015 G_Damage( ent, NULL, attacker, dir, origin, damage, DAMAGE_RADIUS|DAMAGE_NO_TEAM_PROTECTION, MOD_KAMIKAZE );
1016 //
1017 dir[2] = 0;
1018 VectorNormalize(dir);
1019 if ( ent->client ) {
1020 ent->client->ps.velocity[0] = dir[0] * push;
1021 ent->client->ps.velocity[1] = dir[1] * push;
1022 ent->client->ps.velocity[2] = 100;
1023 }
1024 ent->kamikazeShockTime = level.time + 3000;
1025 // }
1026 }
1027 }
1028
1029 /*
1030 ===============
1031 KamikazeDamage
1032 ===============
1033 */
KamikazeDamage(gentity_t * self)1034 static void KamikazeDamage( gentity_t *self ) {
1035 int i;
1036 float t;
1037 gentity_t *ent;
1038 vec3_t newangles;
1039
1040 self->count += 100;
1041
1042 if (self->count >= KAMI_SHOCKWAVE_STARTTIME) {
1043 // shockwave push back
1044 t = self->count - KAMI_SHOCKWAVE_STARTTIME;
1045 KamikazeShockWave(self->s.pos.trBase, self->activator, 25, 400, (int) (float) t * KAMI_SHOCKWAVE_MAXRADIUS / (KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVE_STARTTIME) );
1046 }
1047 //
1048 if (self->count >= KAMI_EXPLODE_STARTTIME) {
1049 // do our damage
1050 t = self->count - KAMI_EXPLODE_STARTTIME;
1051 KamikazeRadiusDamage( self->s.pos.trBase, self->activator, 400, (int) (float) t * KAMI_BOOMSPHERE_MAXRADIUS / (KAMI_IMPLODE_STARTTIME - KAMI_EXPLODE_STARTTIME) );
1052 }
1053
1054 // either cycle or kill self
1055 if( self->count >= KAMI_SHOCKWAVE_ENDTIME ) {
1056 G_FreeEntity( self );
1057 return;
1058 }
1059 self->nextthink = level.time + 100;
1060
1061 // add earth quake effect
1062 newangles[0] = crandom() * 2;
1063 newangles[1] = crandom() * 2;
1064 newangles[2] = 0;
1065 for (i = 0; i < MAX_CLIENTS; i++)
1066 {
1067 ent = &g_entities[i];
1068 if (!ent->inuse)
1069 continue;
1070 if (!ent->client)
1071 continue;
1072
1073 if (ent->client->ps.groundEntityNum != ENTITYNUM_NONE) {
1074 ent->client->ps.velocity[0] += crandom() * 120;
1075 ent->client->ps.velocity[1] += crandom() * 120;
1076 ent->client->ps.velocity[2] = 30 + random() * 25;
1077 }
1078
1079 ent->client->ps.delta_angles[0] += ANGLE2SHORT(newangles[0] - self->movedir[0]);
1080 ent->client->ps.delta_angles[1] += ANGLE2SHORT(newangles[1] - self->movedir[1]);
1081 ent->client->ps.delta_angles[2] += ANGLE2SHORT(newangles[2] - self->movedir[2]);
1082 }
1083 VectorCopy(newangles, self->movedir);
1084 }
1085
1086 /*
1087 ===============
1088 G_StartKamikaze
1089 ===============
1090 */
G_StartKamikaze(gentity_t * ent)1091 void G_StartKamikaze( gentity_t *ent ) {
1092 gentity_t *explosion;
1093 gentity_t *te;
1094 vec3_t snapped;
1095
1096 // start up the explosion logic
1097 explosion = G_Spawn();
1098
1099 explosion->s.eType = ET_EVENTS + EV_KAMIKAZE;
1100 explosion->eventTime = level.time;
1101
1102 if ( ent->client ) {
1103 VectorCopy( ent->s.pos.trBase, snapped );
1104 }
1105 else {
1106 VectorCopy( ent->activator->s.pos.trBase, snapped );
1107 }
1108 SnapVector( snapped ); // save network bandwidth
1109 G_SetOrigin( explosion, snapped );
1110
1111 explosion->classname = "kamikaze";
1112 explosion->s.pos.trType = TR_STATIONARY;
1113
1114 explosion->kamikazeTime = level.time;
1115
1116 explosion->think = KamikazeDamage;
1117 explosion->nextthink = level.time + 100;
1118 explosion->count = 0;
1119 VectorClear(explosion->movedir);
1120
1121 trap_LinkEntity( explosion );
1122
1123 if (ent->client) {
1124 //
1125 explosion->activator = ent;
1126 //
1127 ent->s.eFlags &= ~EF_KAMIKAZE;
1128 // nuke the guy that used it
1129 G_Damage( ent, ent, ent, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_KAMIKAZE );
1130 }
1131 else {
1132 if ( !strcmp(ent->activator->classname, "bodyque") ) {
1133 explosion->activator = &g_entities[ent->activator->r.ownerNum];
1134 }
1135 else {
1136 explosion->activator = ent->activator;
1137 }
1138 }
1139
1140 // play global sound at all clients
1141 te = G_TempEntity(snapped, EV_GLOBAL_TEAM_SOUND );
1142 te->r.svFlags |= SVF_BROADCAST;
1143 te->s.eventParm = GTS_KAMIKAZE;
1144 }
1145 #endif
1146