1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein multiplayer GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).
8
9 RTCW MP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 RTCW MP Source Code 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 RTCW MP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 #include "g_local.h"
30
31 #define MISSILE_PRESTEP_TIME 50
32
33
34 extern void gas_think( gentity_t *gas );
35 extern void gas_touch( gentity_t *gas, gentity_t *other, trace_t *trace );
36 extern void SP_target_smoke( gentity_t *ent );
37
38
39
40 void G_ExplodeMissilePoisonGas( gentity_t *ent );
41 void M_think( gentity_t *ent );
42
43
44 // JPW NERVE
45 // think func for below
Shaker_think(gentity_t * ent)46 void Shaker_think( gentity_t *ent ) {
47 vec3_t vec; // muzzlebounce, JPW NERVE no longer used
48 gentity_t *player;
49 float len, radius = ent->splashDamage, bounceamt;
50 int i;
51 char cmd[64]; //DAJ
52 /* JPW NERVE used for trigger_concussive_dust, currently not working
53 vec3_t mins, maxs; // JPW NERVE
54 static vec3_t range; // JPW NERVE
55 int num,touch[MAX_GENTITIES],scored=0; // JPW NERVE
56 gentity_t *hit, *dirtshake; // JPW NERVE
57 */
58
59 // NERVE - SMF - we only want to call this once now
60 // if (level.time > ent->delay)
61 ent->think = G_FreeEntity;
62 ent->nextthink = level.time + FRAMETIME;
63
64 /*
65 // JPW NERVE check if we're close to trigger_concussive_dust fields
66 range[0] = radius/1.41f; // not exactly right, since we're doing a box trap for a radius, but wtf,
67 range[1] = radius/1.41f; // this is all eye candy anyway
68 range[2] = radius/1.41f;
69
70 VectorAdd(ent->s.origin,range,maxs);
71 VectorSubtract(ent->s.origin,range,mins);
72 num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); // get a list of possibles
73 for ( i=0 ; i<num ; i++ ) {
74 hit = &g_entities[touch[i]];
75 if (hit->s.eType & ET_CONCUSSIVE_TRIGGER) { // add a tempent to shake some shit loose
76 dirtshake = G_Spawn();
77 dirtshake->nextthink = level.time + radius; // 1000 for aircraft flyby, other term for tumble stagger
78 VectorAdd(hit->r.maxs,hit->r.mins,vec);
79 VectorScale(vec,0.5f,vec);
80 VectorCopy(vec,dirtshake->s.pos.trBase);
81 VectorCopy(vec,dirtshake->s.origin);
82 VectorSubtract(vec,ent->s.origin,vec);
83 dirtshake->nextthink = level.time + 5000;//(radius - VectorLength(vec)); // closer the explosion, the longer the dirtshake
84 G_Printf("radius=%f dist=%f\n",radius,VectorLength(vec));
85 dirtshake->think = G_FreeEntity;
86 dirtshake->s.eType = ET_CONCUSSIVE_TRIGGER + ET_EVENTS;
87 dirtshake->s.eFlags |= EF_SMOKINGBLACK;
88 VectorCopy(hit->r.maxs, dirtshake->r.maxs);
89 VectorCopy(hit->r.mins, dirtshake->r.mins);
90 dirtshake->s.pos.trType = TR_STATIONARY;
91 dirtshake->clipmask = 0;
92 dirtshake->r.svFlags &= ~SVF_NOCLIENT;
93 SnapVector(dirtshake->r.maxs);
94 SnapVector(dirtshake->r.mins);
95 SnapVector(dirtshake->s.pos.trDelta);
96 }
97 }
98 */
99
100 for ( i = 0; i < level.maxclients; i++ ) {
101 // skip if not connected
102 if ( level.clients[i].pers.connected != CON_CONNECTED ) {
103 continue;
104 }
105 // skip if in limbo
106 if ( level.clients[i].ps.pm_flags & PMF_LIMBO ) {
107 continue;
108 }
109 // skip if not on same team
110 if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR ) {
111 continue;
112 }
113
114 // found a live one
115 player = &g_entities[i];
116 VectorSubtract( player->r.currentOrigin, ent->s.origin, vec );
117 len = VectorLength( vec );
118
119 if ( len > radius ) { // largest bomb blast = 600
120 continue;
121 }
122
123 // NERVE - SMF - client side camera shake
124 //DAJ BUGFIX va() not doing %f's correctly
125 bounceamt = min( 1.0f, 1.0f - ( len / radius ) );
126 Com_sprintf( cmd, sizeof(cmd), "shake %.4f", bounceamt ); //DAJ
127 trap_SendServerCommand( player->s.clientNum, cmd );
128 //DAJ BUGFIX trap_SendServerCommand( player->s.clientNum, va( "shake %f", &bounceamt));
129 }
130 }
131 // jpw
132
133 // JPW NERVE
134 /*
135 =============
136 Ground_Shaker
137 like concussive_fx but means it
138 =============
139 */
Ground_Shaker(vec3_t origin,float range)140 void Ground_Shaker( vec3_t origin, float range ) {
141 gentity_t *concussive;
142
143 concussive = G_Spawn();
144 VectorCopy( origin, concussive->s.origin );
145 concussive->think = Shaker_think;
146 concussive->nextthink = level.time + FRAMETIME;
147 concussive->splashDamage = range;
148 concussive->delay = level.time + 200; // NERVE - SMF - changed from 1000 to 200
149 return;
150 }
151 // jpw
152
153
154 /*
155 ================
156 G_BounceMissile
157
158 ================
159 */
G_BounceMissile(gentity_t * ent,trace_t * trace)160 void G_BounceMissile( gentity_t *ent, trace_t *trace ) {
161 vec3_t velocity;
162 float dot;
163 int hitTime;
164
165 // Arnout: removed this for MP as well (was already gone from SP)
166 /*
167 // Ridah, if we are a grenade, and we have hit an AI that is waiting to catch us, give them a grenade, and delete ourselves
168 if ((ent->splashMethodOfDeath == MOD_GRENADE_SPLASH) && (g_entities[trace->entityNum].flags & FL_AI_GRENADE_KICK) &&
169 (trace->endpos[2] > g_entities[trace->entityNum].r.currentOrigin[2])) {
170 g_entities[trace->entityNum].grenadeExplodeTime = ent->nextthink;
171 g_entities[trace->entityNum].flags &= ~FL_AI_GRENADE_KICK;
172 Add_Ammo( &g_entities[trace->entityNum], WP_GRENADE_LAUNCHER, 1, qfalse ); //----(SA) modified
173 G_FreeEntity( ent );
174 return;
175 }
176 */
177 // reflect the velocity on the trace plane
178 hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
179 BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
180 dot = DotProduct( velocity, trace->plane.normal );
181 VectorMA( velocity, -2 * dot, trace->plane.normal, ent->s.pos.trDelta );
182
183 // RF, record this for mover pushing
184 if ( trace->plane.normal[2] > 0.2 /*&& VectorLength( ent->s.pos.trDelta ) < 40*/ ) {
185 ent->s.groundEntityNum = trace->entityNum;
186 }
187
188 if ( ent->s.eFlags & EF_BOUNCE_HALF ) {
189 if ( ent->s.eFlags & EF_BOUNCE ) { // both flags marked, do a third type of bounce
190 VectorScale( ent->s.pos.trDelta, 0.35, ent->s.pos.trDelta );
191 } else {
192 VectorScale( ent->s.pos.trDelta, 0.65, ent->s.pos.trDelta );
193 }
194
195 // check for stop
196 if ( trace->plane.normal[2] > 0.2 && VectorLength( ent->s.pos.trDelta ) < 40 ) {
197 //----(SA) make the world the owner of the dynamite, so the player can shoot it after it stops moving
198 if ( ent->s.weapon == WP_DYNAMITE || ent->s.weapon == WP_DYNAMITE2 ) {
199 ent->r.ownerNum = ENTITYNUM_WORLD;
200 }
201 //----(SA) end
202 G_SetOrigin( ent, trace->endpos );
203 ent->s.time = level.time / 4;
204 return;
205 }
206 }
207
208 SnapVector( ent->s.pos.trDelta );
209
210 VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin );
211 VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
212
213 SnapVector( ent->s.pos.trBase );
214 ent->s.pos.trTime = level.time;
215 }
216
217 /*
218 ================
219 G_MissileImpact
220 impactDamage is how much damage the impact will do to func_explosives
221 ================
222 */
G_MissileImpact(gentity_t * ent,trace_t * trace,int impactDamage)223 void G_MissileImpact( gentity_t *ent, trace_t *trace, int impactDamage ) {
224 gentity_t *other;
225 qboolean hitClient = qfalse;
226 vec3_t velocity;
227
228 other = &g_entities[trace->entityNum];
229
230 // handle func_explosives
231 if ( other->classname && Q_stricmp( other->classname, "func_explosive" ) == 0 ) {
232 // the damage is sufficient to "break" the ent (health == 0 is non-breakable)
233 if ( other->health && impactDamage >= other->health ) {
234 // check for other->takedamage needs to be inside the health check since it is
235 // likely that, if successfully destroyed by the missile, in the next runmissile()
236 // update takedamage would be set to '0' and the func_explosive would not be
237 // removed yet, causing a bounce.
238 if ( other->takedamage ) {
239 BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity );
240 G_Damage( other, ent, &g_entities[ent->r.ownerNum], velocity, ent->s.origin, impactDamage, 0, ent->methodOfDeath );
241 }
242
243 // its possible of the func_explosive not to die from this and it
244 // should reflect the missile or explode it not vanish into oblivion
245 if ( other->health <= 0 ) {
246 return;
247 }
248 }
249 }
250
251 // check for bounce
252 if ( !other->takedamage &&
253 ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) ) {
254 G_BounceMissile( ent, trace );
255 // JPW NERVE -- spotter White Phosphorous rounds shouldn't bounce noise
256 if ( !Q_stricmp( ent->classname,"WP" ) ) {
257 return;
258 }
259 // jpw
260 if ( !Q_stricmp( ent->classname, "flamebarrel" ) ) {
261 G_AddEvent( ent, EV_FLAMEBARREL_BOUNCE, 0 );
262 } else {
263 G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 );
264 }
265 return;
266 }
267
268 if ( other->takedamage && ent->s.density == 1 ) {
269 G_ExplodeMissilePoisonGas( ent );
270 return;
271 }
272
273 // impact damage
274 if ( other->takedamage ) {
275 if ( ent->damage ) {
276
277 if ( LogAccuracyHit( other, &g_entities[ent->r.ownerNum] ) ) {
278 if ( g_entities[ent->r.ownerNum].client ) {
279 g_entities[ent->r.ownerNum].client->ps.persistant[PERS_ACCURACY_HITS]++;
280 }
281 hitClient = qtrue;
282 }
283 BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity );
284 if ( VectorLength( velocity ) == 0 ) {
285 velocity[2] = 1; // stepped on a grenade
286 }
287 G_Damage( other, ent, &g_entities[ent->r.ownerNum], velocity,
288 ent->s.origin, ent->damage,
289 0, ent->methodOfDeath );
290 } else // if no damage value, then this is a splash damage grenade only
291 {
292 G_BounceMissile( ent, trace );
293 return;
294 }
295 }
296
297 // is it cheaper in bandwidth to just remove this ent and create a new
298 // one, rather than changing the missile into the explosion?
299
300 if ( other->takedamage && other->client ) {
301 G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) );
302 ent->s.otherEntityNum = other->s.number;
303 } else {
304 // Ridah, try projecting it in the direction it came from, for better decals
305 vec3_t dir;
306 BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, dir );
307 BG_GetMarkDir( dir, trace->plane.normal, dir );
308 G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) );
309 }
310
311 ent->freeAfterEvent = qtrue;
312
313 // change over to a normal entity right at the point of impact
314 ent->s.eType = ET_GENERAL;
315
316 SnapVectorTowards( trace->endpos, ent->s.pos.trBase ); // save net bandwidth
317
318 G_SetOrigin( ent, trace->endpos );
319
320 // splash damage (doesn't apply to person directly hit)
321 if ( ent->splashDamage ) {
322 if ( G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage, ent->splashRadius,
323 other, ent->splashMethodOfDeath ) ) {
324 if ( !hitClient && g_entities[ent->r.ownerNum].client ) {
325 g_entities[ent->r.ownerNum].client->ps.persistant[PERS_ACCURACY_HITS]++;
326 }
327 }
328 }
329
330 trap_LinkEntity( ent );
331 }
332
333 /*
334 ==============
335 Concussive_think
336 ==============
337 */
Concussive_think(gentity_t * ent)338 void Concussive_think( gentity_t *ent ) {
339 gentity_t *player;
340 vec3_t dir;
341 vec3_t kvel;
342 float grav = 24;
343 vec3_t vec;
344 float len;
345
346 if ( level.time > ent->delay ) {
347 ent->think = G_FreeEntity;
348 }
349
350 ent->nextthink = level.time + FRAMETIME;
351
352 if ( g_gametype.integer == GT_SINGLE_PLAYER ) { // JPW NERVE -- in multiplayer this should be handled by ground_shaker
353 player = AICast_FindEntityForName( "player" );
354
355 if ( !player ) {
356 return;
357 }
358
359 VectorSubtract( player->r.currentOrigin, ent->s.origin, vec );
360 len = VectorLength( vec );
361
362 // G_Printf ("len = %5.3f\n", len);
363
364 if ( len > 512 ) {
365 return;
366 }
367
368 VectorSet( dir, 0, 0, 1 );
369 VectorScale( dir, grav, kvel );
370 VectorAdd( player->client->ps.velocity, kvel, player->client->ps.velocity );
371
372 if ( !player->client->ps.pm_time ) {
373 int t;
374
375 t = grav * 2;
376 if ( t < 50 ) {
377 t = 50;
378 }
379 if ( t > 200 ) {
380 t = 200;
381 }
382 player->client->ps.pm_time = t;
383 player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
384 }
385
386 }
387 }
388
389 /*
390 ==============
391 Concussive_fx
392 shake the player
393 caused by explosives (grenades/dynamite/etc.)
394 ==============
395 */
396 //void Concussive_fx (gentity_t *ent)
Concussive_fx(vec3_t origin)397 void Concussive_fx( vec3_t origin ) {
398 // gentity_t *tent;
399 // gentity_t *player;
400
401 gentity_t *concussive;
402
403 concussive = G_Spawn();
404 // VectorCopy (ent->s.origin, concussive->s.origin);
405 VectorCopy( origin, concussive->s.origin );
406 concussive->think = Concussive_think;
407 concussive->nextthink = level.time + FRAMETIME;
408 concussive->delay = level.time + 500;
409 return;
410
411 // Grenade and bomb flinching event
412 /*
413 player = AICast_FindEntityForName( "player" );
414
415 if (!player)
416 return;
417
418 if ( trap_InPVS (player->r.currentOrigin, ent->s.origin) )
419 {
420 tent = G_TempEntity (ent->s.origin, EV_CONCUSSIVE);
421 VectorCopy (ent->s.origin, tent->s.origin);
422 tent->s.density = player->s.number;
423
424 // G_Printf ("sending concussive event\n");
425 }
426 */
427
428 }
429
430
431
432 /*
433 ==============
434 M_think
435 ==============
436 */
M_think(gentity_t * ent)437 void M_think( gentity_t *ent ) {
438 gentity_t *tent;
439
440 ent->count++;
441
442 // if (ent->count == 1)
443 // Concussive_fx (ent); //----(SA) moved to G_ExplodeMissile()
444
445 if ( ent->count == ent->health ) {
446 ent->think = G_FreeEntity;
447 }
448
449 tent = G_TempEntity( ent->s.origin, EV_SMOKE );
450 VectorCopy( ent->s.origin, tent->s.origin );
451 if ( ent->s.density == 1 ) {
452 tent->s.origin[2] += 16;
453 } else {
454 // tent->s.origin[2]+=32;
455 // Note to self Maxx said to lower the spawn loc for the smoke 16 units
456 tent->s.origin[2] += 16;
457 }
458
459 tent->s.time = 3000;
460 tent->s.time2 = 100;
461 tent->s.density = 0;
462 if ( ent->s.density == 1 ) {
463 tent->s.angles2[0] = 16;
464 } else {
465 // Note to self Maxx changed this to 24
466 tent->s.angles2[0] = 24;
467 }
468 tent->s.angles2[1] = 96;
469 tent->s.angles2[2] = 50;
470
471 ent->nextthink = level.time + FRAMETIME;
472
473 }
474
475 /*
476 ================
477 G_ExplodeMissile
478
479 Explode a missile without an impact
480 ================
481 */
G_ExplodeMissile(gentity_t * ent)482 void G_ExplodeMissile( gentity_t *ent ) {
483 vec3_t dir;
484 vec3_t origin;
485 qboolean small = qfalse;
486 qboolean zombiespit = qfalse;
487 int etype;
488
489 BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
490 SnapVector( origin );
491 G_SetOrigin( ent, origin );
492
493 // we don't have a valid direction, so just point straight up
494 dir[0] = dir[1] = 0;
495 dir[2] = 1;
496
497 etype = ent->s.eType;
498
499 ent->s.eType = ET_GENERAL;
500
501 if ( !Q_stricmp( ent->classname, "props_explosion" ) ) {
502 G_AddEvent( ent, EV_MISSILE_MISS_SMALL, DirToByte( dir ) );
503 small = qtrue;
504 }
505 // JPW NERVE
506 else if ( !Q_stricmp( ent->classname, "air strike" ) ) {
507 G_AddEvent( ent, EV_MISSILE_MISS_LARGE, DirToByte( dir ) );
508 small = qfalse;
509 }
510 // jpw
511 else if ( !Q_stricmp( ent->classname, "props_explosion_large" ) ) {
512 G_AddEvent( ent, EV_MISSILE_MISS_LARGE, DirToByte( dir ) );
513 small = qfalse;
514 } else if ( !Q_stricmp( ent->classname, "zombiespit" ) ) {
515 G_AddEvent( ent, EV_SPIT_MISS, DirToByte( dir ) );
516 zombiespit = qtrue;
517 } else if ( !Q_stricmp( ent->classname, "flamebarrel" ) ) {
518 ent->freeAfterEvent = qtrue;
519 trap_LinkEntity( ent );
520 return;
521 } else {
522 G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) );
523 }
524
525 ent->freeAfterEvent = qtrue;
526
527 // splash damage
528 if ( ent->splashDamage ) {
529 if ( G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage, ent->splashRadius, ent, ent->splashMethodOfDeath ) ) { //----(SA)
530 if ( g_entities[ent->r.ownerNum].client ) {
531 g_entities[ent->r.ownerNum].client->ps.persistant[PERS_ACCURACY_HITS]++;
532 }
533 }
534 }
535
536 trap_LinkEntity( ent );
537
538 if ( etype == ET_MISSILE ) {
539 // DHM - Nerve :: ... in single player anyway
540 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
541 if ( ent->s.weapon == WP_VENOM_FULL ) { // no default impact smoke
542 zombiespit = qtrue;
543 } else if ( ent->s.weapon == WP_DYNAMITE || ent->s.weapon == WP_DYNAMITE2 ) {
544 // // shot heard round the world...
545 gentity_t *player;
546 player = AICast_FindEntityForName( "player" );
547 Concussive_fx( player->r.currentOrigin );
548 }
549 }
550 // JPW NERVE -- big nasty dynamite scoring section
551 else {
552 if ( g_gametype.integer >= GT_WOLF ) {
553 if ( ent->s.weapon == WP_DYNAMITE ) { // do some scoring
554 // check if dynamite is in trigger_objective_info field
555 vec3_t mins, maxs;
556 //static vec3_t range = { 18, 18, 18 }; // NOTE can use this to massage throw distance outside trigger field // TTimo unused
557 int i,num,touch[MAX_GENTITIES];
558 gentity_t *hit;
559
560 // NERVE - SMF - made this the actual bounding box of dynamite instead of range
561 VectorAdd( ent->r.currentOrigin, ent->r.mins, mins );
562 VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs );
563 num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
564 VectorAdd( ent->r.currentOrigin, ent->r.mins, mins );
565 VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs );
566
567 for ( i = 0 ; i < num ; i++ ) {
568 hit = &g_entities[touch[i]];
569 if ( !hit->target ) {
570 continue;
571 }
572
573 if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) {
574 continue;
575 }
576 if ( !strcmp( hit->classname,"trigger_objective_info" ) ) {
577 if ( !( hit->spawnflags & ( AXIS_OBJECTIVE | ALLIED_OBJECTIVE ) ) ) {
578 continue;
579 }
580
581 if ( ( ( hit->spawnflags & AXIS_OBJECTIVE ) && ( ent->s.teamNum == TEAM_BLUE ) ) ||
582 ( ( hit->spawnflags & ALLIED_OBJECTIVE ) && ( ent->s.teamNum == TEAM_RED ) ) ) {
583 G_UseTargets( hit,ent );
584 hit->think = G_FreeEntity;
585 hit->nextthink = level.time + FRAMETIME;
586
587 if ( ent->parent->client ) {
588 if ( ent->s.teamNum == ent->parent->client->sess.sessionTeam ) { // make sure player hasn't changed teams -- per atvi req
589 AddScore( ent->parent, hit->accuracy ); // set from map, see g_trigger
590 }
591 }
592 }
593 }
594 }
595 }
596 }
597 // give big weapons the shakey shakey
598 if ( ent->s.weapon == WP_DYNAMITE || ent->s.weapon == WP_PANZERFAUST || ent->s.weapon == WP_GRENADE_LAUNCHER ||
599 ent->s.weapon == WP_GRENADE_PINEAPPLE || ent->s.weapon == WP_ROCKET_LAUNCHER || ent->s.weapon == WP_MORTAR ||
600 ent->s.weapon == WP_ARTY ) {
601 Ground_Shaker( ent->r.currentOrigin, ent->splashDamage * 4 );
602 }
603 return;
604 }
605 // jpw
606 }
607
608
609 if ( !zombiespit ) {
610 gentity_t *Msmoke;
611
612 Msmoke = G_Spawn();
613 VectorCopy( ent->r.currentOrigin, Msmoke->s.origin );
614 if ( small ) {
615 Msmoke->s.density = 1;
616 }
617 Msmoke->think = M_think;
618 Msmoke->nextthink = level.time + FRAMETIME;
619
620 if ( ent->parent && !Q_stricmp( ent->parent->classname, "props_flamebarrel" ) ) {
621 Msmoke->health = 10;
622 } else {
623 Msmoke->health = 5;
624 }
625
626 Concussive_fx( Msmoke->s.origin );
627 }
628 }
629
630 /*
631 ================
632 G_MissileDie
633 ================
634 */
G_MissileDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)635 void G_MissileDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) {
636 if ( inflictor == self ) {
637 return;
638 }
639 self->takedamage = qfalse;
640 self->think = G_ExplodeMissile;
641 self->nextthink = level.time + 10;
642 }
643
644 /*
645 ================
646 G_ExplodeMissilePoisonGas
647
648 Explode a missile without an impact
649 ================
650 */
G_ExplodeMissilePoisonGas(gentity_t * ent)651 void G_ExplodeMissilePoisonGas( gentity_t *ent ) {
652 vec3_t dir;
653 vec3_t origin;
654
655 BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
656 SnapVector( origin );
657 G_SetOrigin( ent, origin );
658
659 // we don't have a valid direction, so just point straight up
660 dir[0] = dir[1] = 0;
661 dir[2] = 1;
662
663 ent->freeAfterEvent = qtrue;
664
665
666 {
667 gentity_t *gas;
668
669 gas = G_Spawn();
670 gas->think = gas_think;
671 gas->nextthink = level.time + FRAMETIME;
672 gas->r.contents = CONTENTS_TRIGGER;
673 gas->touch = gas_touch;
674 gas->health = 100;
675 G_SetOrigin( gas, origin );
676
677 trap_LinkEntity( gas );
678 }
679
680 }
681
682 /*
683 ================
684 G_RunMissile
685
686 ================
687 */
G_RunMissile(gentity_t * ent)688 void G_RunMissile( gentity_t *ent ) {
689 vec3_t origin;
690 trace_t tr;
691 int impactDamage;
692
693 // Ridah, make AI aware of this danger
694 // DHM - Nerve :: Only in single player
695 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
696 AICast_CheckDangerousEntity( ent, DANGER_MISSILE, ent->splashRadius, 0.1, 0.99, qtrue );
697 }
698
699 // get current position
700 BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
701
702 if ( ( ent->clipmask & CONTENTS_BODY ) && ( ent->s.weapon == WP_DYNAMITE || ent->s.weapon == WP_ARTY
703 || ent->s.weapon == WP_GRENADE_LAUNCHER || ent->s.weapon == WP_GRENADE_PINEAPPLE ) ) {
704
705 if ( !ent->s.pos.trDelta[0] && !ent->s.pos.trDelta[1] && !ent->s.pos.trDelta[2] ) {
706 ent->clipmask &= ~CONTENTS_BODY;
707 }
708 }
709
710 // trace a line from the previous position to the current position,
711 // ignoring interactions with the missile owner
712 trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin,
713 ent->r.ownerNum, ent->clipmask );
714
715 VectorCopy( tr.endpos, ent->r.currentOrigin );
716
717 if ( tr.startsolid ) {
718 tr.fraction = 0;
719 }
720
721 trap_LinkEntity( ent );
722
723 if ( tr.fraction != 1 ) {
724 // never explode or bounce on sky
725 if ( tr.surfaceFlags & SURF_NOIMPACT ) {
726 // If grapple, reset owner
727 if ( ent->parent && ent->parent->client && ent->parent->client->hook == ent ) {
728 ent->parent->client->hook = NULL;
729 }
730 G_FreeEntity( ent );
731 return;
732 }
733
734 if ( ent->s.weapon == WP_ROCKET_LAUNCHER || ent->s.weapon == WP_PANZERFAUST ) {
735 impactDamage = 999; // goes through pretty much any func_explosives
736 } else {
737 impactDamage = 20; // "grenade"/"dynamite" // probably adjust this based on velocity
738
739 }
740 G_MissileImpact( ent, &tr, impactDamage );
741
742 if ( ent->s.eType != ET_MISSILE ) {
743 // JPW NERVE
744 if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
745 Ground_Shaker( ent->r.currentOrigin, ent->splashDamage * 4 );
746 }
747 // jpw
748 return; // exploded
749 }
750 }
751
752 // check think function after bouncing
753 G_RunThink( ent );
754 }
755
756 /*
757 ================
758 G_PredictBounceMissile
759
760 ================
761 */
G_PredictBounceMissile(gentity_t * ent,trajectory_t * pos,trace_t * trace,int time)762 void G_PredictBounceMissile( gentity_t *ent, trajectory_t *pos, trace_t *trace, int time ) {
763 vec3_t velocity, origin;
764 float dot;
765 int hitTime;
766
767 BG_EvaluateTrajectory( pos, time, origin );
768
769 // reflect the velocity on the trace plane
770 hitTime = time;
771 BG_EvaluateTrajectoryDelta( pos, hitTime, velocity );
772 dot = DotProduct( velocity, trace->plane.normal );
773 VectorMA( velocity, -2 * dot, trace->plane.normal, pos->trDelta );
774
775 if ( ent->s.eFlags & EF_BOUNCE_HALF ) {
776 if ( ent->s.eFlags & EF_BOUNCE ) { // both flags marked, do a third type of bounce
777 VectorScale( pos->trDelta, 0.35, pos->trDelta );
778 } else {
779 VectorScale( pos->trDelta, 0.65, pos->trDelta );
780 }
781
782 // check for stop
783 if ( trace->plane.normal[2] > 0.2 && VectorLength( pos->trDelta ) < 40 ) {
784 VectorCopy( trace->endpos, pos->trBase );
785 return;
786 }
787 }
788
789 VectorAdd( origin, trace->plane.normal, pos->trBase );
790 pos->trTime = time;
791 }
792
793 /*
794 ================
795 G_PredictMissile
796
797 selfNum is the character that is checking to see what the missile is going to do
798
799 returns qfalse if the missile won't explode, otherwise it'll return the time is it expected to explode
800 ================
801 */
G_PredictMissile(gentity_t * ent,int duration,vec3_t endPos,qboolean allowBounce)802 int G_PredictMissile( gentity_t *ent, int duration, vec3_t endPos, qboolean allowBounce ) {
803 vec3_t origin;
804 trace_t tr;
805 int time;
806 trajectory_t pos;
807 vec3_t org;
808 gentity_t backupEnt;
809
810 pos = ent->s.pos;
811 BG_EvaluateTrajectory( &pos, level.time, org );
812
813 backupEnt = *ent;
814
815 for ( time = level.time + FRAMETIME; time < level.time + duration; time += FRAMETIME ) {
816
817 // get current position
818 BG_EvaluateTrajectory( &pos, time, origin );
819
820 // trace a line from the previous position to the current position,
821 // ignoring interactions with the missile owner
822 trap_Trace( &tr, org, ent->r.mins, ent->r.maxs, origin,
823 ent->r.ownerNum, ent->clipmask );
824
825 VectorCopy( tr.endpos, org );
826
827 if ( tr.startsolid ) {
828 *ent = backupEnt;
829 return qfalse;
830 }
831
832 if ( tr.fraction != 1 ) {
833 // never explode or bounce on sky
834 if ( tr.surfaceFlags & SURF_NOIMPACT ) {
835 *ent = backupEnt;
836 return qfalse;
837 }
838
839 if ( allowBounce && ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) ) {
840 G_PredictBounceMissile( ent, &pos, &tr, time - FRAMETIME + (int)( (float)FRAMETIME * tr.fraction ) );
841 pos.trTime = time;
842 continue;
843 }
844
845 // exploded, so drop out of loop
846 break;
847 }
848 }
849 /*
850 if (!allowBounce && tr.fraction < 1 && tr.entityNum > level.maxclients) {
851 // go back a bit in time, so we can catch it in the air
852 time -= 200;
853 if (time < level.time + FRAMETIME)
854 time = level.time + FRAMETIME;
855 BG_EvaluateTrajectory( &pos, time, org );
856 }
857 */
858
859 // get current position
860 VectorCopy( org, endPos );
861 // set the entity data back
862 *ent = backupEnt;
863 //
864 if ( allowBounce && ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) ) {
865 return ent->nextthink;
866 } else { // it will probably explode before it times out
867 return time;
868 }
869 }
870
871 //=============================================================================
872 // DHM - Nerve :: Server side Flamethrower
873 //=============================================================================
874
875 // copied from cg_flamethrower.c
876 #define FLAME_START_SIZE 1.0
877 #define FLAME_START_MAX_SIZE 100.0 // when the flame is spawned, it should endevour to reach this size
878 #define FLAME_START_SPEED 1200.0 // speed of flame as it leaves the nozzle
879 #define FLAME_MIN_SPEED 60.0
880
881 // these are calculated (don't change)
882 #define FLAME_LENGTH ( FLAMETHROWER_RANGE + 50.0 ) // NOTE: only modify the range, since this should always reflect that range
883
884 #define FLAME_LIFETIME (int)( ( FLAME_LENGTH / FLAME_START_SPEED ) * 1000 ) // life duration in milliseconds
885 #define FLAME_FRICTION_PER_SEC ( 2.0f * FLAME_START_SPEED )
886 #define GET_FLAME_SIZE_SPEED( x ) ( ( (float)x / FLAME_LIFETIME ) / 0.3 ) // x is the current sizeMax
887
888 #define FLAME_THRESHOLD 50
889
G_FlameDamage(gentity_t * self)890 void G_FlameDamage( gentity_t *self ) {
891 gentity_t *body;
892 int entityList[MAX_GENTITIES];
893 int i, e, numListedEntities;
894 float radius, boxradius, dist;
895 vec3_t mins, maxs, point, v;
896 trace_t tr;
897
898 radius = self->speed;
899 boxradius = 1.41421356 * radius; // radius * sqrt(2) for bounding box enlargement
900
901 for ( i = 0 ; i < 3 ; i++ ) {
902 mins[i] = self->r.currentOrigin[i] - boxradius;
903 maxs[i] = self->r.currentOrigin[i] + boxradius;
904 }
905
906 numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
907
908 for ( e = 0 ; e < numListedEntities ; e++ ) {
909 body = &g_entities[entityList[ e ]];
910
911 if ( !body->takedamage ) {
912 continue;
913 }
914
915 // JPW NERVE don't catch fire if invulnerable or same team in no FF
916 if ( body->client ) {
917 if ( body->client->ps.powerups[PW_INVULNERABLE] >= level.time ) {
918 body->flameQuota = 0;
919 body->s.onFireEnd = level.time - 1;
920 continue;
921 }
922 if ( !( g_friendlyFire.integer ) && OnSameTeam( body,self->parent ) ) {
923 continue;
924 }
925 }
926 // jpw
927
928 // JPW NERVE don't catch fire if under water or invulnerable
929 if ( body->waterlevel >= 3 ) {
930 body->flameQuota = 0;
931 body->s.onFireEnd = level.time - 1;
932 continue;
933 }
934 // jpw
935
936 if ( !body->r.bmodel ) {
937 VectorCopy( body->r.currentOrigin, point );
938 if ( body->client ) {
939 point[2] += body->client->ps.viewheight;
940 }
941 VectorSubtract( point, self->r.currentOrigin, v );
942 } else {
943 for ( i = 0 ; i < 3 ; i++ ) {
944 if ( self->s.origin[i] < body->r.absmin[i] ) {
945 v[i] = body->r.absmin[i] - self->r.currentOrigin[i];
946 } else if ( self->r.currentOrigin[i] > body->r.absmax[i] ) {
947 v[i] = self->r.currentOrigin[i] - body->r.absmax[i];
948 } else {
949 v[i] = 0;
950 }
951 }
952 }
953
954 dist = VectorLength( v );
955
956 // The person who shot the flame only burns when within 1/2 the radius
957 if ( body->s.number == self->r.ownerNum && dist >= ( radius * 0.5 ) ) {
958 continue;
959 }
960 if ( dist >= radius ) {
961 continue;
962 }
963
964 // Non-clients that take damage get damaged here
965 if ( !body->client ) {
966 if ( body->health > 0 ) {
967 G_Damage( body, self->parent, self->parent, vec3_origin, self->r.currentOrigin, 2, 0, MOD_FLAMETHROWER );
968 }
969 continue;
970 }
971
972 // JPW NERVE -- do a trace to see if there's a wall btwn. body & flame centroid -- prevents damage through walls
973 trap_Trace( &tr, self->r.currentOrigin, NULL, NULL, point, body->s.number, MASK_SHOT );
974 if ( tr.fraction < 1.0 ) {
975 continue;
976 }
977 // jpw
978
979 // now check the damageQuota to see if we should play a pain animation
980 // first reduce the current damageQuota with time
981 if ( body->flameQuotaTime && body->flameQuota > 0 ) {
982 body->flameQuota -= (int)( ( (float)( level.time - body->flameQuotaTime ) / 1000 ) * 2.5f );
983 if ( body->flameQuota < 0 ) {
984 body->flameQuota = 0;
985 }
986 }
987
988 G_BurnMeGood( self, body );
989 }
990 }
991
G_RunFlamechunk(gentity_t * ent)992 void G_RunFlamechunk( gentity_t *ent ) {
993 vec3_t vel, add;
994 vec3_t neworg;
995 trace_t tr;
996 float speed, dot;
997
998 // Adust the current speed of the chunk
999 if ( level.time - ent->timestamp > 50 ) {
1000 VectorCopy( ent->s.pos.trDelta, vel );
1001 speed = VectorNormalize( vel );
1002 speed -= ( 50.f / 1000.f ) * FLAME_FRICTION_PER_SEC;
1003
1004 if ( speed < FLAME_MIN_SPEED ) {
1005 speed = FLAME_MIN_SPEED;
1006 }
1007
1008 VectorScale( vel, speed, ent->s.pos.trDelta );
1009 } else {
1010 speed = FLAME_START_SPEED;
1011 }
1012
1013 // Move the chunk
1014 VectorScale( ent->s.pos.trDelta, 50.f / 1000.f, add );
1015 VectorAdd( ent->r.currentOrigin, add, neworg );
1016
1017 trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, neworg, ent->r.ownerNum, MASK_SHOT | MASK_WATER ); // JPW NERVE
1018
1019 if ( tr.startsolid ) {
1020 VectorCopy( vec3_origin, ent->s.pos.trDelta );
1021 } else if ( tr.fraction != 1.0f && !( tr.surfaceFlags & SURF_NOIMPACT ) ) {
1022 VectorCopy( tr.endpos, ent->r.currentOrigin );
1023
1024 dot = DotProduct( vel, tr.plane.normal );
1025 VectorMA( vel, -2 * dot, tr.plane.normal, vel );
1026 VectorNormalize( vel );
1027 speed *= 0.5 * ( 0.25 + 0.75 * ( ( dot + 1.0 ) * 0.5 ) );
1028 VectorScale( vel, speed, ent->s.pos.trDelta );
1029 } else {
1030 VectorCopy( neworg, ent->r.currentOrigin );
1031 }
1032
1033 // Do damage to nearby entities, every 100ms
1034 if ( ent->flameQuotaTime <= level.time ) {
1035 ent->flameQuotaTime = level.time + 100;
1036 G_FlameDamage( ent );
1037 }
1038
1039 // Show debugging bbox
1040 if ( g_debugBullets.integer > 3 ) {
1041 gentity_t *bboxEnt;
1042 float size = ent->speed / 2;
1043 vec3_t b1, b2;
1044 vec3_t temp;
1045 VectorSet( temp, -size, -size, -size );
1046 VectorCopy( ent->r.currentOrigin, b1 );
1047 VectorCopy( ent->r.currentOrigin, b2 );
1048 VectorAdd( b1, temp, b1 );
1049 VectorSet( temp, size, size, size );
1050 VectorAdd( b2, temp, b2 );
1051 bboxEnt = G_TempEntity( b1, EV_RAILTRAIL );
1052 VectorCopy( b2, bboxEnt->s.origin2 );
1053 bboxEnt->s.dmgFlags = 1; // ("type")
1054 }
1055
1056 // Adjust the size
1057 if ( ent->speed < FLAME_START_MAX_SIZE ) {
1058 ent->speed += 10.f;
1059
1060 if ( ent->speed > FLAME_START_MAX_SIZE ) {
1061 ent->speed = FLAME_START_MAX_SIZE;
1062 }
1063 }
1064
1065 // Remove after 2 seconds
1066 if ( level.time - ent->timestamp > ( FLAME_LIFETIME - 150 ) ) { // JPW NERVE increased to 350 from 250 to match visuals better
1067 G_FreeEntity( ent );
1068 return;
1069 }
1070
1071 G_RunThink( ent );
1072 }
1073
1074 /*
1075 =================
1076 fire_flamechunk
1077 =================
1078 */
fire_flamechunk(gentity_t * self,vec3_t start,vec3_t dir)1079 gentity_t *fire_flamechunk( gentity_t *self, vec3_t start, vec3_t dir ) {
1080 gentity_t *bolt;
1081
1082 // Only spawn every other frame
1083 if ( self->count2 ) {
1084 self->count2--;
1085 return NULL;
1086 }
1087
1088 self->count2 = 1;
1089 VectorNormalize( dir );
1090
1091 bolt = G_Spawn();
1092 bolt->classname = "flamechunk";
1093
1094 bolt->timestamp = level.time;
1095 bolt->flameQuotaTime = level.time + 50;
1096 bolt->s.eType = ET_FLAMETHROWER_CHUNK;
1097 bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_NOCLIENT;
1098 bolt->s.weapon = self->s.weapon;
1099 bolt->r.ownerNum = self->s.number;
1100 bolt->parent = self;
1101 bolt->methodOfDeath = MOD_FLAMETHROWER;
1102 bolt->clipmask = MASK_MISSILESHOT;
1103
1104 bolt->s.pos.trType = TR_DECCELERATE;
1105 bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
1106 bolt->s.pos.trDuration = 800;
1107
1108 // 'speed' will be the current size radius of the chunk
1109 bolt->speed = FLAME_START_SIZE;
1110 VectorSet( bolt->r.mins, -4, -4, -4 );
1111 VectorSet( bolt->r.maxs, 4, 4, 4 );
1112 VectorCopy( start, bolt->s.pos.trBase );
1113 VectorScale( dir, FLAME_START_SPEED, bolt->s.pos.trDelta );
1114
1115 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
1116 VectorCopy( start, bolt->r.currentOrigin );
1117
1118 return bolt;
1119 }
1120
1121 //=============================================================================
1122
1123 //----(SA) removed unused quake3 weapons.
1124
1125 int G_GetWeaponDamage( int weapon ); // JPW NERVE
1126
DynaSink(gentity_t * self)1127 void DynaSink( gentity_t *self ) {
1128
1129 self->clipmask = 0;
1130 self->r.contents = 0;
1131
1132 if ( self->timestamp < level.time ) {
1133 self->think = G_FreeEntity;
1134 self->nextthink = level.time + FRAMETIME;
1135 return;
1136 }
1137
1138 self->s.pos.trBase[2] -= 0.5f;
1139 self->nextthink = level.time + 50;
1140 }
1141 /*
1142 =================
1143 fire_grenade
1144
1145 NOTE!!!! NOTE!!!!!
1146
1147 This accepts a /non-normalized/ direction vector to allow specification
1148 of how hard it's thrown. Please scale the vector before calling.
1149
1150 =================
1151 */
fire_grenade(gentity_t * self,vec3_t start,vec3_t dir,int grenadeWPID)1152 gentity_t *fire_grenade( gentity_t *self, vec3_t start, vec3_t dir, int grenadeWPID ) {
1153 gentity_t *bolt;
1154 qboolean noExplode = qfalse;
1155
1156 bolt = G_Spawn();
1157
1158 // no self->client for shooter_grenade's
1159 if ( self->client && self->client->ps.grenadeTimeLeft ) {
1160 // TTimo
1161 // was: if( g_gametype.integer < GT_WOLF && grenadeWPID == WP_DYNAMITE || grenadeWPID == WP_DYNAMITE2) {
1162 // gcc: suggest parentheses around && within ||
1163 if ( g_gametype.integer < GT_WOLF && ( grenadeWPID == WP_DYNAMITE || grenadeWPID == WP_DYNAMITE2 ) ) { // remove any fraction of a 5 second 'click'
1164 self->client->ps.grenadeTimeLeft *= 5;
1165 self->client->ps.grenadeTimeLeft -= ( self->client->ps.grenadeTimeLeft % 5000 );
1166 self->client->ps.grenadeTimeLeft += 5000;
1167 if ( self->client->ps.grenadeTimeLeft < 5000 ) { // allow dropping of dynamite that won't explode (for shooting)
1168 noExplode = qtrue;
1169 }
1170 }
1171
1172 if ( !noExplode ) {
1173 bolt->nextthink = level.time + self->client->ps.grenadeTimeLeft;
1174 }
1175 } else {
1176 bolt->nextthink = level.time + 2500;
1177 }
1178
1179 // TTimo
1180 // was: if( g_gametype.integer >= GT_WOLF && grenadeWPID == WP_DYNAMITE || grenadeWPID == WP_DYNAMITE2 ) {
1181 // gcc: suggest parentheses around && within ||
1182 if ( g_gametype.integer >= GT_WOLF && ( grenadeWPID == WP_DYNAMITE || grenadeWPID == WP_DYNAMITE2 ) ) {
1183 noExplode = qtrue;
1184 bolt->nextthink = level.time + 15000;
1185 bolt->think = DynaSink;
1186 bolt->timestamp = level.time + 16500;
1187 }
1188
1189 // no self->client for shooter_grenade's
1190 if ( self->client ) {
1191 self->client->ps.grenadeTimeLeft = 0; // reset grenade timer
1192
1193 }
1194 if ( !noExplode ) {
1195 bolt->think = G_ExplodeMissile;
1196 }
1197
1198 bolt->s.eType = ET_MISSILE;
1199 bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_BROADCAST;
1200 bolt->s.weapon = grenadeWPID;
1201 bolt->r.ownerNum = self->s.number;
1202 bolt->parent = self;
1203
1204 // JPW NERVE -- commented out bolt->damage and bolt->splashdamage, override with G_GetWeaponDamage()
1205 // so it works with different netgame balance. didn't uncomment bolt->damage on dynamite 'cause its so *special*
1206 bolt->damage = G_GetWeaponDamage( grenadeWPID ); // overridden for dynamite
1207 bolt->splashDamage = G_GetWeaponDamage( grenadeWPID );
1208 // jpw
1209
1210 switch ( grenadeWPID ) {
1211 case WP_GRENADE_LAUNCHER:
1212 bolt->classname = "grenade";
1213 // bolt->damage = 100;
1214 // bolt->splashDamage = 100;
1215 if ( g_gametype.integer >= GT_WOLF ) {
1216 bolt->splashRadius = 300;
1217 } else {
1218 bolt->splashRadius = 150;
1219 }
1220 bolt->methodOfDeath = MOD_GRENADE;
1221 bolt->splashMethodOfDeath = MOD_GRENADE_SPLASH;
1222 bolt->s.eFlags = EF_BOUNCE_HALF | EF_BOUNCE;
1223 break;
1224 case WP_GRENADE_PINEAPPLE:
1225 bolt->classname = "grenade";
1226 // bolt->damage = 80;
1227 // bolt->splashDamage = 80;
1228 bolt->splashRadius = 300;
1229 bolt->methodOfDeath = MOD_GRENADE;
1230 bolt->splashMethodOfDeath = MOD_GRENADE_SPLASH;
1231 bolt->s.eFlags = EF_BOUNCE_HALF | EF_BOUNCE;
1232 break;
1233 // JPW NERVE
1234 case WP_SMOKE_GRENADE:
1235 bolt->classname = "grenade";
1236 bolt->s.eFlags = EF_BOUNCE_HALF | EF_BOUNCE;
1237 break;
1238 // jpw
1239 case WP_DYNAMITE:
1240 case WP_DYNAMITE2:
1241
1242 bolt->accuracy = 0; // JPW NERVE sets to score below if dynamite is in trigger_objective_info & it's an objective
1243 trap_SendServerCommand( self - g_entities, "cp \"Dynamite is set, but NOT armed!\"" );
1244 // differentiate non-armed dynamite with non-pulsing dlight
1245 if ( self->client )
1246 bolt->s.teamNum = self->client->sess.sessionTeam + 4;
1247 bolt->classname = "dynamite";
1248 bolt->damage = 0;
1249 // bolt->splashDamage = 300;
1250 bolt->splashRadius = 400;
1251 bolt->methodOfDeath = MOD_DYNAMITE;
1252 bolt->splashMethodOfDeath = MOD_DYNAMITE_SPLASH;
1253 bolt->s.eFlags = ( EF_BOUNCE | EF_BOUNCE_HALF ); // EF_BOUNCE_HEAVY;
1254
1255 // dynamite is shootable
1256 // JPW NERVE only in single player
1257 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
1258 bolt->health = 5;
1259 bolt->takedamage = qtrue;
1260 bolt->die = G_MissileDie;
1261 } else {
1262 bolt->health = 5;
1263 bolt->takedamage = qfalse;
1264 }
1265 // jpw
1266
1267 bolt->r.contents = CONTENTS_CORPSE; // (player can walk through)
1268
1269 // nope - this causes the dynamite to impact on the players bb when he throws it.
1270 // will try setting it when it settles
1271 // bolt->r.ownerNum = ENTITYNUM_WORLD; // (SA) make the world the owner of the dynamite, so the player can shoot it without modifying the bullet code to ignore players id for hits
1272
1273 // small target cube
1274 VectorSet( bolt->r.mins, -12, -12, 0 );
1275 VectorCopy( bolt->r.mins, bolt->r.absmin );
1276 VectorSet( bolt->r.maxs, 12, 12, 20 );
1277 VectorCopy( bolt->r.maxs, bolt->r.absmax );
1278 break;
1279 }
1280
1281 // JPW NERVE -- blast radius proportional to damage
1282 if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
1283 bolt->splashRadius = G_GetWeaponDamage( grenadeWPID );
1284 }
1285 // jpw
1286
1287 bolt->clipmask = MASK_MISSILESHOT;
1288
1289 bolt->s.pos.trType = TR_GRAVITY;
1290 bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
1291 VectorCopy( start, bolt->s.pos.trBase );
1292 VectorCopy( dir, bolt->s.pos.trDelta );
1293 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
1294
1295 VectorCopy( start, bolt->r.currentOrigin );
1296
1297 return bolt;
1298 }
1299
1300 //=============================================================================
1301
1302
1303 /*
1304 ==============
1305 fire_speargun
1306 ==============
1307 */
1308 #define SPEAR_WATERSPEED 400
1309 #define SPEAR_AIRSPEED 700
1310
fire_speargun(gentity_t * self,vec3_t start,vec3_t dir)1311 gentity_t *fire_speargun( gentity_t *self, vec3_t start, vec3_t dir ) {
1312 gentity_t *bolt;
1313
1314 VectorNormalize( dir );
1315
1316 bolt = G_Spawn();
1317 bolt->classname = "spear";
1318 bolt->nextthink = level.time + 10000;
1319 bolt->think = G_ExplodeMissile;
1320 bolt->s.eType = ET_MISSILE;
1321
1322 bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
1323
1324 bolt->s.weapon = WP_SPEARGUN;
1325 bolt->r.ownerNum = self->s.number;
1326 bolt->parent = self;
1327 bolt->damage = 15; // (SA) spear damage here
1328 bolt->splashDamage = 0;
1329 bolt->methodOfDeath = MOD_SPEARGUN;
1330 bolt->clipmask = MASK_MISSILESHOT;
1331
1332 bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
1333 VectorCopy( start, bolt->s.pos.trBase );
1334
1335 // (SA) Kind of a cheap hack to make the speargun worthless out of the water
1336 // This'll probably change to something better
1337 if ( ( trap_PointContents( start, -1 ) & CONTENTS_WATER ) ) {
1338 bolt->s.pos.trType = TR_LINEAR;
1339 VectorScale( dir, SPEAR_WATERSPEED, bolt->s.pos.trDelta );
1340 } else {
1341 bolt->s.pos.trType = TR_GRAVITY_LOW;
1342 VectorScale( dir, SPEAR_AIRSPEED, bolt->s.pos.trDelta );
1343 }
1344
1345 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
1346 VectorCopy( start, bolt->r.currentOrigin );
1347
1348
1349 return bolt;
1350 }
1351
1352
1353 /*
1354 =================
1355 fire_rocket
1356 =================
1357 */
fire_rocket(gentity_t * self,vec3_t start,vec3_t dir)1358 gentity_t *fire_rocket( gentity_t *self, vec3_t start, vec3_t dir ) {
1359 gentity_t *bolt;
1360
1361 VectorNormalize( dir );
1362
1363 bolt = G_Spawn();
1364 bolt->classname = "rocket";
1365 bolt->nextthink = level.time + 20000; // push it out a little
1366 bolt->think = G_ExplodeMissile;
1367 bolt->s.eType = ET_MISSILE;
1368 bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_BROADCAST;
1369
1370 //DHM - Nerve :: Use the correct weapon in multiplayer
1371 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
1372 bolt->s.weapon = WP_ROCKET_LAUNCHER;
1373 } else {
1374 bolt->s.weapon = self->s.weapon;
1375 }
1376
1377 bolt->r.ownerNum = self->s.number;
1378 bolt->parent = self;
1379 bolt->damage = G_GetWeaponDamage( WP_ROCKET_LAUNCHER ); // JPW NERVE
1380 bolt->splashDamage = G_GetWeaponDamage( WP_ROCKET_LAUNCHER ); // JPW NERVE
1381 // JPW NERVE
1382 if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
1383 bolt->splashRadius = G_GetWeaponDamage( WP_ROCKET_LAUNCHER );
1384 } else {
1385 bolt->splashRadius = 120;
1386 }
1387 // jpw
1388 bolt->methodOfDeath = MOD_ROCKET;
1389 bolt->splashMethodOfDeath = MOD_ROCKET_SPLASH;
1390 // bolt->clipmask = MASK_SHOT;
1391 bolt->clipmask = MASK_MISSILESHOT;
1392
1393 bolt->s.pos.trType = TR_LINEAR;
1394 bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
1395 VectorCopy( start, bolt->s.pos.trBase );
1396 // JPW NERVE
1397 if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
1398 VectorScale( dir,2500,bolt->s.pos.trDelta );
1399 } else {
1400 VectorScale( dir, 900, bolt->s.pos.trDelta );
1401 }
1402 // jpw
1403 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
1404 VectorCopy( start, bolt->r.currentOrigin );
1405
1406 return bolt;
1407 }
1408
1409 // Rafael flamebarrel
1410 /*
1411 ======================
1412 fire_flamebarrel
1413 ======================
1414 */
1415
fire_flamebarrel(gentity_t * self,vec3_t start,vec3_t dir)1416 gentity_t *fire_flamebarrel( gentity_t *self, vec3_t start, vec3_t dir ) {
1417 gentity_t *bolt;
1418
1419 VectorNormalize( dir );
1420
1421 bolt = G_Spawn();
1422 bolt->classname = "flamebarrel";
1423 bolt->nextthink = level.time + 3000;
1424 bolt->think = G_ExplodeMissile;
1425 bolt->s.eType = ET_FLAMEBARREL;
1426 bolt->s.eFlags = EF_BOUNCE_HALF;
1427 bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
1428 bolt->s.weapon = WP_ROCKET_LAUNCHER;
1429 bolt->r.ownerNum = self->s.number;
1430 bolt->parent = self;
1431 bolt->damage = 100;
1432 bolt->splashDamage = 20;
1433 bolt->splashRadius = 60;
1434 bolt->s.eFlags |= EF_SMOKINGBLACK;
1435
1436 bolt->methodOfDeath = MOD_ROCKET;
1437 bolt->splashMethodOfDeath = MOD_ROCKET_SPLASH;
1438
1439 bolt->clipmask = MASK_MISSILESHOT;
1440
1441 bolt->s.pos.trType = TR_GRAVITY;
1442 bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
1443 VectorCopy( start, bolt->s.pos.trBase );
1444 VectorScale( dir, 900 + ( crandom() * 100 ), bolt->s.pos.trDelta );
1445 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
1446 VectorCopy( start, bolt->r.currentOrigin );
1447
1448 return bolt;
1449 }
1450
1451
1452 // Rafael sniper
1453 /*
1454 =================
1455 fire_lead
1456 =================
1457 */
1458
fire_lead(gentity_t * self,vec3_t start,vec3_t dir,int damage)1459 void fire_lead( gentity_t *self, vec3_t start, vec3_t dir, int damage ) {
1460
1461 trace_t tr;
1462 vec3_t end;
1463 gentity_t *tent;
1464 gentity_t *traceEnt;
1465 vec3_t forward, right, up;
1466 vec3_t angles;
1467 float r, u;
1468 qboolean anti_tank_enable = qfalse;
1469
1470 r = crandom() * self->random;
1471 u = crandom() * self->random;
1472
1473 vectoangles( dir, angles );
1474 AngleVectors( angles, forward, right, up );
1475
1476 VectorMA( start, 8192, forward, end );
1477 VectorMA( end, r, right, end );
1478 VectorMA( end, u, up, end );
1479
1480 trap_Trace( &tr, start, NULL, NULL, end, self->s.number, MASK_SHOT );
1481 if ( tr.surfaceFlags & SURF_NOIMPACT ) {
1482 return;
1483 }
1484
1485 traceEnt = &g_entities[ tr.entityNum ];
1486
1487 // snap the endpos to integers, but nudged towards the line
1488 SnapVectorTowards( tr.endpos, start );
1489
1490 // send bullet impact
1491 if ( traceEnt->takedamage && traceEnt->client ) {
1492 tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH );
1493 tent->s.eventParm = traceEnt->s.number;
1494 } else {
1495 // Ridah, bullet impact should reflect off surface
1496 vec3_t reflect;
1497 float dot;
1498
1499 tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL );
1500
1501 dot = DotProduct( forward, tr.plane.normal );
1502 VectorMA( forward, -2 * dot, tr.plane.normal, reflect );
1503 VectorNormalize( reflect );
1504
1505 tent->s.eventParm = DirToByte( reflect );
1506 // done.
1507 }
1508 tent->s.otherEntityNum = self->s.number;
1509
1510 if ( traceEnt->takedamage ) {
1511
1512 if ( self->s.weapon == WP_SNIPER
1513 && traceEnt->s.eType == ET_MOVER
1514 && traceEnt->aiName[0] ) {
1515 anti_tank_enable = qtrue;
1516 }
1517
1518 if ( anti_tank_enable ) {
1519 self->s.weapon = WP_ROCKET_LAUNCHER;
1520 }
1521
1522 G_Damage( traceEnt, self, self, forward, tr.endpos,
1523 damage, 0, MOD_MACHINEGUN );
1524
1525 if ( anti_tank_enable ) {
1526 self->s.weapon = WP_SNIPER;
1527 }
1528
1529 }
1530
1531 }
1532
1533
1534 // Rafael sniper
1535 // visible
1536
1537 /*
1538 ==============
1539 visible
1540 ==============
1541 */
visible(gentity_t * self,gentity_t * other)1542 qboolean visible( gentity_t *self, gentity_t *other ) {
1543 // vec3_t spot1;
1544 // vec3_t spot2;
1545 trace_t tr;
1546 gentity_t *traceEnt;
1547
1548 trap_Trace( &tr, self->r.currentOrigin, NULL, NULL, other->r.currentOrigin, self->s.number, MASK_SHOT );
1549
1550 traceEnt = &g_entities[ tr.entityNum ];
1551
1552 if ( traceEnt == other ) {
1553 return qtrue;
1554 }
1555
1556 return qfalse;
1557
1558 }
1559
1560
1561
1562 /*
1563 ==============
1564 fire_mortar
1565 dir is a non-normalized direction/power vector
1566 ==============
1567 */
fire_mortar(gentity_t * self,vec3_t start,vec3_t dir)1568 gentity_t *fire_mortar( gentity_t *self, vec3_t start, vec3_t dir ) {
1569 gentity_t *bolt;
1570
1571 // VectorNormalize (dir);
1572
1573 if ( self->spawnflags ) {
1574 gentity_t *tent;
1575 tent = G_TempEntity( self->s.pos.trBase, EV_MORTAREFX );
1576 tent->s.density = self->spawnflags; // send smoke and muzzle flash flags
1577 VectorCopy( self->s.pos.trBase, tent->s.origin );
1578 VectorCopy( self->s.apos.trBase, tent->s.angles );
1579 }
1580
1581 bolt = G_Spawn();
1582 bolt->classname = "mortar";
1583 bolt->nextthink = level.time + 20000; // push it out a little
1584 bolt->think = G_ExplodeMissile;
1585 bolt->s.eType = ET_MISSILE;
1586
1587 bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_BROADCAST; // broadcast sound. not multiplayer friendly, but for mortars it should be okay
1588 // bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
1589
1590 bolt->s.weapon = WP_MORTAR;
1591 bolt->r.ownerNum = self->s.number;
1592 bolt->parent = self;
1593 bolt->damage = G_GetWeaponDamage( WP_MORTAR ); // JPW NERVE
1594 bolt->splashDamage = G_GetWeaponDamage( WP_MORTAR ); // JPW NERVE
1595 bolt->splashRadius = 120;
1596 bolt->methodOfDeath = MOD_MORTAR;
1597 bolt->splashMethodOfDeath = MOD_MORTAR_SPLASH;
1598 bolt->clipmask = MASK_MISSILESHOT;
1599
1600 bolt->s.pos.trType = TR_GRAVITY;
1601 bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
1602 VectorCopy( start, bolt->s.pos.trBase );
1603 // VectorScale( dir, 900, bolt->s.pos.trDelta );
1604 VectorCopy( dir, bolt->s.pos.trDelta );
1605 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
1606 VectorCopy( start, bolt->r.currentOrigin );
1607
1608 return bolt;
1609 }
1610
1611
1612 /*
1613 =================
1614 fire_nail
1615 =================
1616 */
1617 #define NAILGUN_SPREAD 1000
1618
fire_nail(gentity_t * self,vec3_t start,vec3_t forward,vec3_t right,vec3_t up)1619 gentity_t *fire_nail( gentity_t *self, vec3_t start, vec3_t forward, vec3_t right, vec3_t up ) {
1620 gentity_t *bolt;
1621 vec3_t dir;
1622 vec3_t end;
1623 float r, u, scale;
1624
1625 bolt = G_Spawn();
1626 bolt->classname = "nail";
1627 bolt->nextthink = level.time + 10000;
1628 bolt->think = G_ExplodeMissile;
1629 bolt->s.eType = ET_MISSILE;
1630 bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
1631 // bolt->s.weapon = WP_NAILGUN;
1632 bolt->s.weapon = WP_VENOM_FULL;
1633 bolt->r.ownerNum = self->s.number;
1634 bolt->parent = self;
1635 bolt->damage = G_GetWeaponDamage( WP_VENOM_FULL );
1636 // bolt->methodOfDeath = MOD_NAIL;
1637 bolt->methodOfDeath = MOD_VENOM_FULL;
1638 bolt->clipmask = MASK_SHOT;
1639 bolt->target_ent = NULL;
1640
1641 bolt->s.pos.trType = TR_LINEAR;
1642 bolt->s.pos.trTime = level.time;
1643 VectorCopy( start, bolt->s.pos.trBase );
1644
1645 r = random() * M_PI * 2.0f;
1646 u = sin( r ) * crandom() * NAILGUN_SPREAD * 16;
1647 r = cos( r ) * crandom() * NAILGUN_SPREAD * 16;
1648 VectorMA( start, 8192 * 16, forward, end );
1649 VectorMA( end, r, right, end );
1650 VectorMA( end, u, up, end );
1651 VectorSubtract( end, start, dir );
1652 VectorNormalize( dir );
1653
1654 // JPW NERVE
1655 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
1656 scale = 555 + random() * 1800;
1657 } else {
1658 scale = 1200 + random() * 2500;
1659 }
1660 // jpw
1661 VectorScale( dir, scale, bolt->s.pos.trDelta );
1662 SnapVector( bolt->s.pos.trDelta );
1663
1664 VectorCopy( start, bolt->r.currentOrigin );
1665
1666 return bolt;
1667 }
1668
1669
1670 /*
1671 =================
1672 fire_prox
1673 =================
1674 */
fire_prox(gentity_t * self,vec3_t start,vec3_t dir)1675 gentity_t *fire_prox( gentity_t *self, vec3_t start, vec3_t dir ) {
1676 gentity_t *bolt;
1677
1678 VectorNormalize( dir );
1679
1680 bolt = G_Spawn();
1681 bolt->classname = "prox mine";
1682 bolt->nextthink = level.time + 3000;
1683 bolt->think = G_ExplodeMissile;
1684 bolt->s.eType = ET_MISSILE;
1685 bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
1686 // bolt->s.weapon = WP_PROX_LAUNCHER;
1687 bolt->s.eFlags = 0;
1688 bolt->r.ownerNum = self->s.number;
1689 bolt->parent = self;
1690 bolt->damage = 0;
1691 bolt->splashDamage = 100;
1692 bolt->splashRadius = 150;
1693 // bolt->methodOfDeath = MOD_PROXIMITY_MINE;
1694 // bolt->splashMethodOfDeath = MOD_PROXIMITY_MINE;
1695 bolt->clipmask = MASK_SHOT;
1696 bolt->target_ent = NULL;
1697 // count is used to check if the prox mine left the player bbox
1698 // if count == 1 then the prox mine left the player bbox and can attack to it
1699 bolt->count = 0;
1700
1701 //FIXME: we prolly wanna abuse another field
1702 // bolt->s.generic1 = self->client->sess.sessionTeam;
1703
1704 bolt->s.pos.trType = TR_GRAVITY;
1705 bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
1706 VectorCopy( start, bolt->s.pos.trBase );
1707 VectorScale( dir, 700, bolt->s.pos.trDelta );
1708 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
1709
1710 VectorCopy( start, bolt->r.currentOrigin );
1711
1712 return bolt;
1713 }
1714