1 #include "g_local.h"
2
3 #define INCLUDE_ETF_RIFLE 1
4 #define INCLUDE_PROX 1
5 //#define INCLUDE_FLAMETHROWER 1
6 //#define INCLUDE_INCENDIARY 1
7 #define INCLUDE_NUKE 1
8 #define INCLUDE_MELEE 1
9 #define INCLUDE_TESLA 1
10 #define INCLUDE_BEAMS 1
11
12 extern void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed);
13 extern void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
14 extern void droptofloor (edict_t *ent);
15 extern void Grenade_Explode (edict_t *ent);
16
17 extern void drawbbox (edict_t *ent);
18
19 #ifdef INCLUDE_ETF_RIFLE
20 /*
21 ========================
22 fire_flechette
23 ========================
24 */
flechette_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)25 void flechette_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
26 {
27 vec3_t dir;
28
29 if (other == self->owner)
30 return;
31
32 if (surf && (surf->flags & SURF_SKY))
33 {
34 G_FreeEdict (self);
35 return;
36 }
37
38 if (self->client)
39 PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
40
41 if (other->takedamage)
42 {
43 //gi.dprintf("t_damage %s\n", other->classname);
44 T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
45 self->dmg, self->dmg_radius, DAMAGE_NO_REG_ARMOR, MOD_ETF_RIFLE);
46 }
47 else
48 {
49 if(!plane)
50 VectorClear (dir);
51 else
52 VectorScale (plane->normal, 256, dir);
53 gi.WriteByte (svc_temp_entity);
54 gi.WriteByte (TE_FLECHETTE);
55 gi.WritePosition (self->s.origin);
56 gi.WriteDir (dir);
57 gi.multicast (self->s.origin, MULTICAST_PVS);
58
59 // T_RadiusDamage(self, self->owner, 24, self, 48, MOD_ETF_RIFLE);
60 }
61
62 G_FreeEdict (self);
63 }
64
fire_flechette(edict_t * self,vec3_t start,vec3_t dir,int damage,int speed,int kick)65 void fire_flechette (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int kick)
66 {
67 edict_t *flechette;
68
69 VectorNormalize (dir);
70
71 flechette = G_Spawn();
72 VectorCopy (start, flechette->s.origin);
73 VectorCopy (start, flechette->s.old_origin);
74 vectoangles2 (dir, flechette->s.angles);
75
76 VectorScale (dir, speed, flechette->velocity);
77 flechette->movetype = MOVETYPE_FLYMISSILE;
78 flechette->clipmask = MASK_SHOT;
79 flechette->solid = SOLID_BBOX;
80 flechette->s.renderfx = RF_FULLBRIGHT;
81 VectorClear (flechette->mins);
82 VectorClear (flechette->maxs);
83
84 flechette->s.modelindex = gi.modelindex ("models/proj/flechette/tris.md2");
85
86 // flechette->s.sound = gi.soundindex (""); // FIXME - correct sound!
87 flechette->owner = self;
88 flechette->touch = flechette_touch;
89 flechette->nextthink = level.time + 8000/speed;
90 flechette->think = G_FreeEdict;
91 flechette->dmg = damage;
92 flechette->dmg_radius = kick;
93
94 gi.linkentity (flechette);
95
96 if (self->client)
97 check_dodge (self, flechette->s.origin, dir, speed);
98 }
99 #endif
100
101 // **************************
102 // PROX
103 // **************************
104
105 #ifdef INCLUDE_PROX
106 #define PROX_TIME_TO_LIVE 45 // 45, 30, 15, 10
107 #define PROX_TIME_DELAY 0.5
108 #define PROX_BOUND_SIZE 96
109 #define PROX_DAMAGE_RADIUS 192
110 #define PROX_HEALTH 20
111 #define PROX_DAMAGE 90
112
113 //===============
114 //===============
Prox_Explode(edict_t * ent)115 void Prox_Explode (edict_t *ent)
116 {
117 vec3_t origin;
118 edict_t *owner;
119
120 // free the trigger field
121
122 //PMM - changed teammaster to "mover" .. owner of the field is the prox
123 if(ent->teamchain && ent->teamchain->owner == ent)
124 G_FreeEdict(ent->teamchain);
125
126 owner = ent;
127 if(ent->teammaster)
128 {
129 owner = ent->teammaster;
130 PlayerNoise(owner, ent->s.origin, PNOISE_IMPACT);
131 }
132
133 // play quad sound if appopriate
134 if (ent->dmg > PROX_DAMAGE)
135 gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
136
137 ent->takedamage = DAMAGE_NO;
138 T_RadiusDamage(ent, owner, ent->dmg, ent, PROX_DAMAGE_RADIUS, MOD_PROX);
139
140 VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
141 gi.WriteByte (svc_temp_entity);
142 if (ent->groundentity)
143 gi.WriteByte (TE_GRENADE_EXPLOSION);
144 else
145 gi.WriteByte (TE_ROCKET_EXPLOSION);
146 gi.WritePosition (origin);
147 gi.multicast (ent->s.origin, MULTICAST_PVS);
148
149 G_FreeEdict (ent);
150 }
151
152 //===============
153 //===============
prox_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)154 void prox_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
155 {
156 // gi.dprintf("prox_die\n");
157 // if set off by another prox, delay a little (chained explosions)
158 if (strcmp(inflictor->classname, "prox"))
159 {
160 self->takedamage = DAMAGE_NO;
161 Prox_Explode(self);
162 }
163 else
164 {
165 self->takedamage = DAMAGE_NO;
166 self->think = Prox_Explode;
167 self->nextthink = level.time + FRAMETIME;
168 }
169 }
170
171 //===============
172 //===============
Prox_Field_Touch(edict_t * ent,edict_t * other,cplane_t * plane,csurface_t * surf)173 void Prox_Field_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
174 {
175 edict_t *prox;
176
177 if (!(other->svflags & SVF_MONSTER) && !other->client)
178 return;
179
180 // trigger the prox mine if it's still there, and still mine.
181 prox = ent->owner;
182
183 if (other == prox) // don't set self off
184 return;
185
186 if (prox->think == Prox_Explode) // we're set to blow!
187 {
188 // if ((g_showlogic) && (g_showlogic->value))
189 // gi.dprintf ("%f - prox already gone off!\n", level.time);
190 return;
191 }
192
193 if(prox->teamchain == ent)
194 {
195 gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxwarn.wav"), 1, ATTN_NORM, 0);
196 prox->think = Prox_Explode;
197 prox->nextthink = level.time + PROX_TIME_DELAY;
198 return;
199 }
200
201 ent->solid = SOLID_NOT;
202 G_FreeEdict(ent);
203 }
204
205 //===============
206 //===============
prox_seek(edict_t * ent)207 void prox_seek (edict_t *ent)
208 {
209 if(level.time > ent->wait)
210 {
211 Prox_Explode(ent);
212 }
213 else
214 {
215 ent->s.frame++;
216 if(ent->s.frame > 13)
217 ent->s.frame = 9;
218 ent->think = prox_seek;
219 ent->nextthink = level.time + 0.1;
220 }
221 }
222
223 //===============
224 //===============
prox_open(edict_t * ent)225 void prox_open (edict_t *ent)
226 {
227 edict_t *search;
228
229 search = NULL;
230 // gi.dprintf("prox_open %d\n", ent->s.frame);
231 // gi.dprintf("%f\n", ent->velocity[2]);
232 if(ent->s.frame == 9) // end of opening animation
233 {
234 // set the owner to NULL so the owner can shoot it, etc. needs to be done here so the owner
235 // doesn't get stuck on it while it's opening if fired at point blank wall
236 ent->s.sound = 0;
237 ent->owner = NULL;
238 if(ent->teamchain)
239 ent->teamchain->touch = Prox_Field_Touch;
240 while ((search = findradius(search, ent->s.origin, PROX_DAMAGE_RADIUS+10)) != NULL)
241 {
242 if (!search->classname) // tag token and other weird shit
243 continue;
244
245 // if (!search->takedamage)
246 // continue;
247 // if it's a monster or player with health > 0
248 // or it's a player start point
249 // and we can see it
250 // blow up
251 if (
252 (
253 (((search->svflags & SVF_MONSTER) || (search->client)) && (search->health > 0)) ||
254 (
255 (deathmatch->value) &&
256 (
257 (!strcmp(search->classname, "info_player_deathmatch")) ||
258 (!strcmp(search->classname, "info_player_start")) ||
259 (!strcmp(search->classname, "info_player_coop")) ||
260 (!strcmp(search->classname, "misc_teleporter_dest"))
261 )
262 )
263 )
264 && (visible (search, ent))
265 )
266 {
267 gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxwarn.wav"), 1, ATTN_NORM, 0);
268 Prox_Explode (ent);
269 return;
270 }
271 }
272
273 if (strong_mines && (strong_mines->value))
274 ent->wait = level.time + PROX_TIME_TO_LIVE;
275 else
276 {
277 switch (ent->dmg/PROX_DAMAGE)
278 {
279 case 1:
280 ent->wait = level.time + PROX_TIME_TO_LIVE;
281 break;
282 case 2:
283 ent->wait = level.time + 30;
284 break;
285 case 4:
286 ent->wait = level.time + 15;
287 break;
288 case 8:
289 ent->wait = level.time + 10;
290 break;
291 default:
292 // if ((g_showlogic) && (g_showlogic->value))
293 // gi.dprintf ("prox with unknown multiplier %d!\n", ent->dmg/PROX_DAMAGE);
294 ent->wait = level.time + PROX_TIME_TO_LIVE;
295 break;
296 }
297 }
298
299 // ent->wait = level.time + PROX_TIME_TO_LIVE;
300 ent->think = prox_seek;
301 ent->nextthink = level.time + 0.2;
302 }
303 else
304 {
305 if (ent->s.frame == 0)
306 gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxopen.wav"), 1, ATTN_NORM, 0);
307 //ent->s.sound = gi.soundindex ("weapons/proxopen.wav");
308 ent->s.frame++;
309 ent->think = prox_open;
310 ent->nextthink = level.time + 0.05;
311 }
312 }
313
314 //===============
315 //===============
prox_land(edict_t * ent,edict_t * other,cplane_t * plane,csurface_t * surf)316 void prox_land (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
317 {
318 edict_t *field;
319 vec3_t dir;
320 vec3_t forward, right, up;
321 int makeslave = 0;
322 int movetype = MOVETYPE_NONE;
323 int stick_ok = 0;
324 vec3_t land_point;
325
326 // must turn off owner so owner can shoot it and set it off
327 // moved to prox_open so owner can get away from it if fired at pointblank range into
328 // wall
329 // ent->owner = NULL;
330
331 // if ((g_showlogic) && (g_showlogic->value))
332 // gi.dprintf ("land - %2.2f %2.2f %2.2f\n", ent->velocity[0], ent->velocity[1], ent->velocity[2]);
333
334 if (surf && (surf->flags & SURF_SKY))
335 {
336 G_FreeEdict(ent);
337 return;
338 }
339
340 if (plane->normal)
341 {
342 VectorMA (ent->s.origin, -10.0, plane->normal, land_point);
343 if (gi.pointcontents (land_point) & (CONTENTS_SLIME|CONTENTS_LAVA))
344 {
345 Prox_Explode (ent);
346 return;
347 }
348 }
349
350 if ((other->svflags & SVF_MONSTER) || other->client || (other->svflags & SVF_DAMAGEABLE))
351 {
352 if(other != ent->teammaster)
353 Prox_Explode(ent);
354
355 return;
356 }
357
358 #define STOP_EPSILON 0.1
359
360 else if (other != world)
361 {
362 //Here we need to check to see if we can stop on this entity.
363 //Note that plane can be NULL
364
365 //PMM - code stolen from g_phys (ClipVelocity)
366 vec3_t out;
367 float backoff, change;
368 int i;
369
370 if (!plane->normal) // this happens if you hit a point object, maybe other cases
371 {
372 // Since we can't tell what's going to happen, just blow up
373 // if ((g_showlogic) && (g_showlogic->value))
374 // gi.dprintf ("bad normal for surface, exploding!\n");
375
376 Prox_Explode(ent);
377 return;
378 }
379
380 if ((other->movetype == MOVETYPE_PUSH) && (plane->normal[2] > 0.7))
381 stick_ok = 1;
382 else
383 stick_ok = 0;
384
385 backoff = DotProduct (ent->velocity, plane->normal) * 1.5;
386 for (i=0 ; i<3 ; i++)
387 {
388 change = plane->normal[i]*backoff;
389 out[i] = ent->velocity[i] - change;
390 if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
391 out[i] = 0;
392 }
393
394 if (out[2] > 60)
395 return;
396
397 movetype = MOVETYPE_BOUNCE;
398
399 // if we're here, we're going to stop on an entity
400 if (stick_ok)
401 { // it's a happy entity
402 VectorCopy (vec3_origin, ent->velocity);
403 VectorCopy (vec3_origin, ent->avelocity);
404 }
405 else // no-stick. teflon time
406 {
407 if (plane->normal[2] > 0.7)
408 {
409 // if ((g_showlogic) && (g_showlogic->value))
410 // gi.dprintf ("stuck on entity, blowing up!\n");
411
412 Prox_Explode(ent);
413 return;
414 }
415 return;
416 }
417 }
418 else if (other->s.modelindex != 1)
419 return;
420
421 vectoangles2 (plane->normal, dir);
422 AngleVectors (dir, forward, right, up);
423
424 if (gi.pointcontents (ent->s.origin) & (CONTENTS_LAVA|CONTENTS_SLIME))
425 {
426 Prox_Explode (ent);
427 return;
428 }
429
430 field = G_Spawn();
431
432 VectorCopy (ent->s.origin, field->s.origin);
433 VectorClear(field->velocity);
434 VectorClear(field->avelocity);
435 VectorSet(field->mins, -PROX_BOUND_SIZE, -PROX_BOUND_SIZE, -PROX_BOUND_SIZE);
436 VectorSet(field->maxs, PROX_BOUND_SIZE, PROX_BOUND_SIZE, PROX_BOUND_SIZE);
437 field->movetype = MOVETYPE_NONE;
438 field->solid = SOLID_TRIGGER;
439 field->owner = ent;
440 field->classname = "prox_field";
441 field->teammaster = ent;
442 gi.linkentity (field);
443
444 VectorClear(ent->velocity);
445 VectorClear(ent->avelocity);
446 // rotate to vertical
447 dir[PITCH] = dir[PITCH] + 90;
448 VectorCopy (dir, ent->s.angles);
449 ent->takedamage = DAMAGE_AIM;
450 ent->movetype = movetype; // either bounce or none, depending on whether we stuck to something
451 ent->die = prox_die;
452 ent->teamchain = field;
453 ent->health = PROX_HEALTH;
454 ent->nextthink = level.time + 0.05;
455 ent->think = prox_open;
456 ent->touch = NULL;
457 ent->solid = SOLID_BBOX;
458 // record who we're attached to
459 // ent->teammaster = other;
460
461 gi.linkentity(ent);
462 }
463
464 //===============
465 //===============
fire_prox(edict_t * self,vec3_t start,vec3_t aimdir,int damage_multiplier,int speed)466 void fire_prox (edict_t *self, vec3_t start, vec3_t aimdir, int damage_multiplier, int speed)
467 {
468 edict_t *prox;
469 vec3_t dir;
470 vec3_t forward, right, up;
471
472 vectoangles2 (aimdir, dir);
473 AngleVectors (dir, forward, right, up);
474
475 // if ((g_showlogic) && (g_showlogic->value))
476 // gi.dprintf ("start %s aim %s speed %d\n", vtos(start), vtos(aimdir), speed);
477 prox = G_Spawn();
478 VectorCopy (start, prox->s.origin);
479 VectorScale (aimdir, speed, prox->velocity);
480 VectorMA (prox->velocity, 200 + crandom() * 10.0, up, prox->velocity);
481 VectorMA (prox->velocity, crandom() * 10.0, right, prox->velocity);
482 VectorCopy (dir, prox->s.angles);
483 prox->s.angles[PITCH]-=90;
484 prox->movetype = MOVETYPE_BOUNCE;
485 prox->solid = SOLID_BBOX;
486 prox->s.effects |= EF_GRENADE;
487 prox->clipmask = MASK_SHOT|CONTENTS_LAVA|CONTENTS_SLIME;
488 prox->s.renderfx |= RF_IR_VISIBLE;
489 //FIXME - this needs to be bigger. Has other effects, though. Maybe have to change origin to compensate
490 // so it sinks in correctly. Also in lavacheck, might have to up the distance
491 VectorSet (prox->mins, -6, -6, -6);
492 VectorSet (prox->maxs, 6, 6, 6);
493 prox->s.modelindex = gi.modelindex ("models/weapons/g_prox/tris.md2");
494 prox->owner = self;
495 prox->teammaster = self;
496 prox->touch = prox_land;
497 // prox->nextthink = level.time + PROX_TIME_TO_LIVE;
498 prox->think = Prox_Explode;
499 prox->dmg = PROX_DAMAGE*damage_multiplier;
500 prox->classname = "prox";
501 prox->svflags |= SVF_DAMAGEABLE;
502 prox->flags |= FL_MECHANICAL;
503
504 switch (damage_multiplier)
505 {
506 case 1:
507 prox->nextthink = level.time + PROX_TIME_TO_LIVE;
508 break;
509 case 2:
510 prox->nextthink = level.time + 30;
511 break;
512 case 4:
513 prox->nextthink = level.time + 15;
514 break;
515 case 8:
516 prox->nextthink = level.time + 10;
517 break;
518 default:
519 // if ((g_showlogic) && (g_showlogic->value))
520 // gi.dprintf ("prox with unknown multiplier %d!\n", damage_multiplier);
521 prox->nextthink = level.time + PROX_TIME_TO_LIVE;
522 break;
523 }
524
525 gi.linkentity (prox);
526 }
527 #endif
528
529 // *************************
530 // FLAMETHROWER
531 // *************************
532
533 #ifdef INCLUDE_FLAMETHROWER
534 #define FLAMETHROWER_RADIUS 8
535
fire_remove(edict_t * ent)536 void fire_remove (edict_t *ent)
537 {
538 if(ent == ent->owner->teamchain)
539 ent->owner->teamchain = NULL;
540
541 G_FreeEdict(ent);
542 }
543
fire_flame(edict_t * self,vec3_t start,vec3_t aimdir,int damage,int speed)544 void fire_flame (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed)
545 {
546 edict_t *flame;
547 vec3_t dir;
548 vec3_t forward, right, up;
549
550 vectoangles2 (aimdir, dir);
551 AngleVectors (dir, forward, right, up);
552
553 flame = G_Spawn();
554
555 // the origin is the first control point, put it speed forward.
556 VectorMA(start, speed, forward, flame->s.origin);
557
558 // record that velocity
559 VectorScale (aimdir, speed, flame->velocity);
560
561 VectorCopy (dir, flame->s.angles);
562 flame->movetype = MOVETYPE_NONE;
563 flame->solid = SOLID_NOT;
564
565 VectorSet(flame->mins, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS);
566 VectorSet(flame->maxs, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS);
567
568 flame->s.sound = gi.soundindex ("weapons/flame.wav");
569 flame->owner = self;
570 flame->dmg = damage;
571 flame->classname = "flame";
572
573 // clear control points and velocities
574 VectorCopy (flame->s.origin, flame->flameinfo.pos1);
575 VectorCopy (flame->velocity, flame->flameinfo.vel1);
576 VectorCopy (flame->s.origin, flame->flameinfo.pos2);
577 VectorCopy (flame->velocity, flame->flameinfo.vel2);
578 VectorCopy (flame->s.origin, flame->flameinfo.pos3);
579 VectorCopy (flame->velocity, flame->flameinfo.vel3);
580 VectorCopy (flame->s.origin, flame->flameinfo.pos4);
581
582 // hook flame stream to owner
583 self->teamchain = flame;
584
585 gi.linkentity (flame);
586 }
587
588 // fixme - change to use start location, not entity origin
fire_maintain(edict_t * ent,edict_t * flame,vec3_t start,vec3_t aimdir,int damage,int speed)589 void fire_maintain (edict_t *ent, edict_t *flame, vec3_t start, vec3_t aimdir, int damage, int speed)
590 {
591 trace_t tr;
592
593 // move the control points out the appropriate direction and velocity
594 VectorAdd(flame->flameinfo.pos3, flame->flameinfo.vel3, flame->flameinfo.pos4);
595 VectorAdd(flame->flameinfo.pos2, flame->flameinfo.vel2, flame->flameinfo.pos3);
596 VectorAdd(flame->flameinfo.pos1, flame->flameinfo.vel1, flame->flameinfo.pos2);
597 VectorAdd(flame->s.origin, flame->velocity, flame->flameinfo.pos1);
598
599 // move the velocities for the control points
600 VectorCopy(flame->flameinfo.vel2, flame->flameinfo.vel3);
601 VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel2);
602 VectorCopy(flame->velocity, flame->flameinfo.vel1);
603
604 // set velocity and location for new control point 0.
605 VectorMA(start, speed, aimdir, flame->s.origin);
606 VectorScale(aimdir, speed, flame->velocity);
607
608 //
609 // does it hit a wall? if so, when?
610 //
611
612 // player fire point to flame origin.
613 tr = gi.trace(start, flame->mins, flame->maxs,
614 flame->s.origin, flame, MASK_SHOT);
615 if(tr.fraction == 1.0)
616 {
617 // origin to point 1
618 tr = gi.trace(flame->s.origin, flame->mins, flame->maxs,
619 flame->flameinfo.pos1, flame, MASK_SHOT);
620 if(tr.fraction == 1.0)
621 {
622 // point 1 to point 2
623 tr = gi.trace(flame->flameinfo.pos1, flame->mins, flame->maxs,
624 flame->flameinfo.pos2, flame, MASK_SHOT);
625 if(tr.fraction == 1.0)
626 {
627 // point 2 to point 3
628 tr = gi.trace(flame->flameinfo.pos2, flame->mins, flame->maxs,
629 flame->flameinfo.pos3, flame, MASK_SHOT);
630 if(tr.fraction == 1.0)
631 {
632 // point 3 to point 4, point 3 valid
633 tr = gi.trace(flame->flameinfo.pos3, flame->mins, flame->maxs,
634 flame->flameinfo.pos4, flame, MASK_SHOT);
635 if(tr.fraction < 1.0) // point 4 blocked
636 {
637 VectorCopy(tr.endpos, flame->flameinfo.pos4);
638 }
639 }
640 else // point 3 blocked, point 2 valid
641 {
642 VectorCopy(flame->flameinfo.vel2, flame->flameinfo.vel3);
643 VectorCopy(tr.endpos, flame->flameinfo.pos3);
644 VectorCopy(tr.endpos, flame->flameinfo.pos4);
645 }
646 }
647 else // point 2 blocked, point 1 valid
648 {
649 VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel2);
650 VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel3);
651 VectorCopy(tr.endpos, flame->flameinfo.pos2);
652 VectorCopy(tr.endpos, flame->flameinfo.pos3);
653 VectorCopy(tr.endpos, flame->flameinfo.pos4);
654 }
655 }
656 else // point 1 blocked, origin valid
657 {
658 VectorCopy(flame->velocity, flame->flameinfo.vel1);
659 VectorCopy(flame->velocity, flame->flameinfo.vel2);
660 VectorCopy(flame->velocity, flame->flameinfo.vel3);
661 VectorCopy(tr.endpos, flame->flameinfo.pos1);
662 VectorCopy(tr.endpos, flame->flameinfo.pos2);
663 VectorCopy(tr.endpos, flame->flameinfo.pos3);
664 VectorCopy(tr.endpos, flame->flameinfo.pos4);
665 }
666 }
667 else // origin blocked!
668 {
669 // gi.dprintf("point 2 blocked\n");
670 VectorCopy(flame->velocity, flame->flameinfo.vel1);
671 VectorCopy(flame->velocity, flame->flameinfo.vel2);
672 VectorCopy(flame->velocity, flame->flameinfo.vel3);
673 VectorCopy(tr.endpos, flame->s.origin);
674 VectorCopy(tr.endpos, flame->flameinfo.pos1);
675 VectorCopy(tr.endpos, flame->flameinfo.pos2);
676 VectorCopy(tr.endpos, flame->flameinfo.pos3);
677 VectorCopy(tr.endpos, flame->flameinfo.pos4);
678 }
679
680 if(tr.fraction < 1.0 && tr.ent->takedamage)
681 {
682 T_Damage (tr.ent, flame, ent, flame->velocity, tr.endpos, tr.plane.normal,
683 damage, 0, DAMAGE_NO_KNOCKBACK | DAMAGE_ENERGY | DAMAGE_FIRE);
684 }
685
686 gi.linkentity(flame);
687
688 gi.WriteByte (svc_temp_entity);
689 gi.WriteByte (TE_FLAME);
690 gi.WriteShort(ent - g_edicts);
691 gi.WriteShort(6);
692 gi.WritePosition (start);
693 gi.WritePosition (flame->s.origin);
694 gi.WritePosition (flame->flameinfo.pos1);
695 gi.WritePosition (flame->flameinfo.pos2);
696 gi.WritePosition (flame->flameinfo.pos3);
697 gi.WritePosition (flame->flameinfo.pos4);
698 gi.multicast (flame->s.origin, MULTICAST_PVS);
699 }
700
701 /*QUAKED trap_flameshooter (1 0 0) (-8 -8 -8) (8 8 8)
702 */
703 #define FLAMESHOOTER_VELOCITY 50
704 #define FLAMESHOOTER_DAMAGE 20
705 #define FLAMESHOOTER_BURST_VELOCITY 300
706 #define FLAMESHOOTER_BURST_DAMAGE 30
707
708 //#define FLAMESHOOTER_PUFF 1
709 #define FLAMESHOOTER_STREAM 1
710
flameshooter_think(edict_t * self)711 void flameshooter_think (edict_t *self)
712 {
713 vec3_t forward, right, up;
714 edict_t *flame;
715
716 if(self->delay)
717 {
718 if(self->teamchain)
719 fire_remove (self->teamchain);
720 return;
721 }
722
723 self->s.angles[1] += self->speed;
724 if(self->s.angles[1] > 135 || self->s.angles[1] < 45)
725 self->speed = -self->speed;
726
727 AngleVectors (self->s.angles, forward, right, up);
728
729 #ifdef FLAMESHOOTER_STREAM
730 flame = self->teamchain;
731 if(!self->teamchain)
732 fire_flame (self, self->s.origin, forward, FLAMESHOOTER_DAMAGE, FLAMESHOOTER_VELOCITY);
733 else
734 fire_maintain (self, flame, self->s.origin, forward, FLAMESHOOTER_DAMAGE, FLAMESHOOTER_VELOCITY);
735
736 self->think = flameshooter_think;
737 self->nextthink = level.time + 0.05;
738 #else
739 fire_burst (self, self->s.origin, forward, FLAMESHOOTER_BURST_DAMAGE, FLAMESHOOTER_BURST_VELOCITY);
740
741 self->think = flameshooter_think;
742 self->nextthink = level.time + 0.1;
743 #endif
744 }
745
flameshooter_use(edict_t * self,edict_t * other,edict_t * activator)746 void flameshooter_use (edict_t *self, edict_t *other, edict_t *activator)
747 {
748 if(self->delay)
749 {
750 self->delay = 0;
751 self->think = flameshooter_think;
752 self->nextthink = level.time + 0.1;
753 }
754 else
755 self->delay = 1;
756 }
757
SP_trap_flameshooter(edict_t * self)758 void SP_trap_flameshooter(edict_t *self)
759 {
760 vec3_t tempAngles;
761
762 self->solid = SOLID_NOT;
763 self->movetype = MOVETYPE_NONE;
764
765 self->delay = 0;
766
767 self->use = flameshooter_use;
768 if(self->delay == 0)
769 {
770 self->think = flameshooter_think;
771 self->nextthink = level.time + 0.1;
772 }
773
774 // self->flags |= FL_NOCLIENT;
775
776 self->speed = 10;
777
778 // self->speed = 0; // FIXME this stops the spraying
779
780 VectorCopy(self->s.angles, tempAngles);
781
782 if (!VectorCompare(self->s.angles, vec3_origin))
783 G_SetMovedir (self->s.angles, self->movedir);
784
785 VectorCopy(tempAngles, self->s.angles);
786
787 // gi.setmodel (self, self->model);
788 gi.linkentity (self);
789 }
790
791 // *************************
792 // fire_burst
793 // *************************
794
795 #define FLAME_BURST_MAX_SIZE 64
796 #define FLAME_BURST_FRAMES 20
797 #define FLAME_BURST_MIDPOINT 10
798
fire_burst_touch(edict_t * ent,edict_t * other,cplane_t * plane,csurface_t * surf)799 void fire_burst_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
800 {
801 int powerunits;
802 int damage, radius;
803 vec3_t origin;
804
805 if (surf && (surf->flags & SURF_SKY))
806 {
807 // gi.dprintf("Hit sky. Removed\n");
808 G_FreeEdict (ent);
809 return;
810 }
811
812 if(other == ent->owner || ent == other)
813 return;
814
815 // don't let flame puffs blow each other up
816 if(other->classname && !strcmp(other->classname, ent->classname))
817 return;
818
819 if(ent->waterlevel)
820 {
821 // gi.dprintf("Hit water. Removed\n");
822 G_FreeEdict(ent);
823 }
824
825 if(!(other->svflags & SVF_MONSTER) && !other->client)
826 {
827 powerunits = FLAME_BURST_FRAMES - ent->s.frame;
828 damage = powerunits * 6;
829 radius = powerunits * 4;
830
831 // T_RadiusDamage (inflictor, attacker, damage, ignore, radius)
832 T_RadiusDamage(ent, ent->owner, damage, ent, radius, DAMAGE_FIRE);
833
834 // gi.dprintf("Hit world: %d pts, %d rad\n", damage, radius);
835
836 // calculate position for the explosion entity
837 VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
838
839 gi.WriteByte (svc_temp_entity);
840 gi.WriteByte (TE_PLAIN_EXPLOSION);
841 gi.WritePosition (origin);
842 gi.multicast (ent->s.origin, MULTICAST_PVS);
843
844 G_FreeEdict (ent);
845 }
846 }
847
fire_burst_think(edict_t * self)848 void fire_burst_think (edict_t *self)
849 {
850 int current_radius;
851
852 if(self->waterlevel)
853 {
854 G_FreeEdict(self);
855 return;
856 }
857
858 self->s.frame++;
859 if(self->s.frame >= FLAME_BURST_FRAMES)
860 {
861 G_FreeEdict(self);
862 return;
863 }
864
865 else if(self->s.frame < FLAME_BURST_MIDPOINT)
866 {
867 current_radius = (FLAME_BURST_MAX_SIZE / FLAME_BURST_MIDPOINT) * self->s.frame;
868 }
869 else
870 {
871 current_radius = (FLAME_BURST_MAX_SIZE / FLAME_BURST_MIDPOINT) * (FLAME_BURST_FRAMES - self->s.frame);
872 }
873
874 if(self->s.frame == 3)
875 self->s.skinnum = 1;
876 else if (self->s.frame == 7)
877 self->s.skinnum = 2;
878 else if (self->s.frame == 10)
879 self->s.skinnum = 3;
880 else if (self->s.frame == 13)
881 self->s.skinnum = 4;
882 else if (self->s.frame == 16)
883 self->s.skinnum = 5;
884 else if (self->s.frame == 19)
885 self->s.skinnum = 6;
886
887 if(current_radius < 8)
888 current_radius = 8;
889 else if(current_radius > FLAME_BURST_MAX_SIZE)
890 current_radius = FLAME_BURST_MAX_SIZE;
891
892 T_RadiusDamage(self, self->owner, self->dmg, self, current_radius, DAMAGE_FIRE);
893
894 self->think = fire_burst_think;
895 self->nextthink = level.time + 0.1;
896 }
897
fire_burst(edict_t * self,vec3_t start,vec3_t aimdir,int damage,int speed)898 void fire_burst (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed)
899 {
900 edict_t *flame;
901 vec3_t dir;
902 vec3_t baseVel;
903 vec3_t forward, right, up;
904
905 vectoangles2 (aimdir, dir);
906 AngleVectors (dir, forward, right, up);
907
908 flame = G_Spawn();
909 VectorCopy(start, flame->s.origin);
910 // VectorScale (aimdir, speed, flame->velocity);
911
912 // scale down so only 30% of player's velocity is taken into account.
913 VectorScale (self->velocity, 0.3, baseVel);
914 VectorMA(baseVel, speed, aimdir, flame->velocity);
915
916 VectorCopy (dir, flame->s.angles);
917 flame->movetype = MOVETYPE_FLY;
918 flame->solid = SOLID_TRIGGER;
919
920 VectorSet(flame->mins, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS);
921 VectorSet(flame->maxs, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS);
922
923 flame->s.sound = gi.soundindex ("weapons/flame.wav");
924 flame->s.modelindex = gi.modelindex ("models/projectiles/puff/tris.md2");
925 flame->owner = self;
926 flame->touch = fire_burst_touch;
927 flame->think = fire_burst_think;
928 flame->nextthink = level.time + 0.1;
929 flame->dmg = damage;
930 flame->classname = "flameburst";
931 flame->s.effects = EF_FIRE_PUFF;
932
933 gi.linkentity (flame);
934 }
935 #endif
936
937 // *************************
938 // INCENDIARY GRENADES
939 // *************************
940
941 #ifdef INCLUDE_INCENDIARY
FireThink(edict_t * ent)942 void FireThink (edict_t *ent)
943 {
944 if(level.time > ent->wait)
945 G_FreeEdict(ent);
946 else
947 {
948 ent->s.frame++;
949 if(ent->s.frame>10)
950 ent->s.frame = 0;
951 ent->nextthink = level.time + 0.05;
952 ent->think = FireThink;
953 }
954 }
955
956 #define FIRE_HEIGHT 64
957 #define FIRE_RADIUS 64
958 #define FIRE_DAMAGE 3
959 #define FIRE_DURATION 15
960
StartFire(edict_t * fireOwner,vec3_t fireOrigin,float fireDuration,float fireDamage)961 edict_t *StartFire(edict_t *fireOwner, vec3_t fireOrigin, float fireDuration, float fireDamage)
962 {
963 edict_t *fire;
964
965 fire = G_Spawn();
966 VectorCopy (fireOrigin, fire->s.origin);
967 fire->movetype = MOVETYPE_TOSS;
968 fire->solid = SOLID_TRIGGER;
969 VectorSet(fire->mins, -FIRE_RADIUS, -FIRE_RADIUS, 0);
970 VectorSet(fire->maxs, FIRE_RADIUS, FIRE_RADIUS, FIRE_HEIGHT);
971
972 fire->s.sound = gi.soundindex ("weapons/incend.wav");
973 fire->s.modelindex = gi.modelindex ("models/objects/fire/tris.md2");
974
975 fire->owner = fireOwner;
976 fire->touch = hurt_touch;
977 fire->nextthink = level.time + 0.05;
978 fire->wait = level.time + fireDuration;
979 fire->think = FireThink;
980 // fire->nextthink = level.time + fireDuration;
981 // fire->think = G_FreeEdict;
982 fire->dmg = fireDamage;
983 fire->classname = "incendiary_fire";
984
985 gi.linkentity (fire);
986
987 // gi.sound (fire, CHAN_VOICE, gi.soundindex ("weapons/incend.wav"), 1, ATTN_NORM, 0);
988 return fire;
989 }
990
Incendiary_Explode(edict_t * ent)991 static void Incendiary_Explode (edict_t *ent)
992 {
993 vec3_t origin;
994
995 if (ent->owner->client)
996 PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
997
998 //FIXME: if we are onground then raise our Z just a bit since we are a point?
999 T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, DAMAGE_FIRE);
1000
1001 VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
1002 gi.WriteByte (svc_temp_entity);
1003 if (ent->groundentity)
1004 gi.WriteByte (TE_GRENADE_EXPLOSION);
1005 else
1006 gi.WriteByte (TE_ROCKET_EXPLOSION);
1007 gi.WritePosition (origin);
1008 gi.multicast (ent->s.origin, MULTICAST_PVS);
1009
1010 StartFire(ent->owner, ent->s.origin, FIRE_DURATION, FIRE_DAMAGE);
1011
1012 G_FreeEdict (ent);
1013
1014 }
1015
Incendiary_Touch(edict_t * ent,edict_t * other,cplane_t * plane,csurface_t * surf)1016 static void Incendiary_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
1017 {
1018 if (other == ent->owner)
1019 return;
1020
1021 if (surf && (surf->flags & SURF_SKY))
1022 {
1023 G_FreeEdict (ent);
1024 return;
1025 }
1026
1027 if (!(other->svflags & SVF_MONSTER) && !(ent->client))
1028 // if (!other->takedamage)
1029 {
1030 if (ent->spawnflags & 1)
1031 {
1032 if (random() > 0.5)
1033 gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
1034 else
1035 gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
1036 }
1037 else
1038 {
1039 if (random() > 0.5)
1040 gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0);
1041 else
1042 gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb2b.wav"), 1, ATTN_NORM, 0);
1043 }
1044 return;
1045 }
1046
1047 Incendiary_Explode (ent);
1048 }
1049
fire_incendiary_grenade(edict_t * self,vec3_t start,vec3_t aimdir,int damage,int speed,float timer,float damage_radius)1050 void fire_incendiary_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius)
1051 {
1052 edict_t *grenade;
1053 vec3_t dir;
1054 vec3_t forward, right, up;
1055
1056 vectoangles2 (aimdir, dir);
1057 AngleVectors (dir, forward, right, up);
1058
1059 grenade = G_Spawn();
1060 VectorCopy (start, grenade->s.origin);
1061 VectorScale (aimdir, speed, grenade->velocity);
1062 VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
1063 VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
1064 VectorSet (grenade->avelocity, 300, 300, 300);
1065 grenade->movetype = MOVETYPE_BOUNCE;
1066 grenade->clipmask = MASK_SHOT;
1067 grenade->solid = SOLID_BBOX;
1068 grenade->s.effects |= EF_GRENADE;
1069 // if (self->client)
1070 // grenade->s.effects &= ~EF_TELEPORT;
1071 VectorClear (grenade->mins);
1072 VectorClear (grenade->maxs);
1073 grenade->s.modelindex = gi.modelindex ("models/projectiles/incend/tris.md2");
1074 grenade->owner = self;
1075 grenade->touch = Incendiary_Touch;
1076 grenade->nextthink = level.time + timer;
1077 grenade->think = Incendiary_Explode;
1078 grenade->dmg = damage;
1079 grenade->dmg_radius = damage_radius;
1080 grenade->classname = "incendiary_grenade";
1081
1082 gi.linkentity (grenade);
1083 }
1084 #endif
1085
1086 // *************************
1087 // MELEE WEAPONS
1088 // *************************
1089
1090 #ifdef INCLUDE_MELEE
fire_player_melee(edict_t * self,vec3_t start,vec3_t aim,int reach,int damage,int kick,int quiet,int mod)1091 void fire_player_melee (edict_t *self, vec3_t start, vec3_t aim, int reach, int damage, int kick, int quiet, int mod)
1092 {
1093 vec3_t forward, right, up;
1094 vec3_t v;
1095 vec3_t point;
1096 trace_t tr;
1097
1098 vectoangles2 (aim, v);
1099 AngleVectors (v, forward, right, up);
1100 VectorNormalize (forward);
1101 VectorMA( start, reach, forward, point);
1102
1103 //see if the hit connects
1104 tr = gi.trace(start, NULL, NULL, point, self, MASK_SHOT);
1105 if(tr.fraction == 1.0)
1106 {
1107 if(!quiet)
1108 gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/swish.wav"), 1, ATTN_NORM, 0);
1109 //FIXME some sound here?
1110 return;
1111 }
1112
1113 if(tr.ent->takedamage == DAMAGE_YES || tr.ent->takedamage == DAMAGE_AIM)
1114 {
1115 // pull the player forward if you do damage
1116 VectorMA(self->velocity, 75, forward, self->velocity);
1117 VectorMA(self->velocity, 75, up, self->velocity);
1118
1119 // do the damage
1120 // FIXME - make the damage appear at right spot and direction
1121 if(mod == MOD_CHAINFIST)
1122 T_Damage (tr.ent, self, self, vec3_origin, tr.ent->s.origin, vec3_origin, damage, kick/2,
1123 DAMAGE_DESTROY_ARMOR | DAMAGE_NO_KNOCKBACK, mod);
1124 else
1125 T_Damage (tr.ent, self, self, vec3_origin, tr.ent->s.origin, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, mod);
1126
1127 if(!quiet)
1128 gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/meatht.wav"), 1, ATTN_NORM, 0);
1129 }
1130 else
1131 {
1132 if(!quiet)
1133 gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/tink1.wav"), 1, ATTN_NORM, 0);
1134
1135 VectorScale (tr.plane.normal, 256, point);
1136 gi.WriteByte (svc_temp_entity);
1137 gi.WriteByte (TE_GUNSHOT);
1138 gi.WritePosition (tr.endpos);
1139 gi.WriteDir (point);
1140 gi.multicast (tr.endpos, MULTICAST_PVS);
1141 }
1142 }
1143 #endif
1144
1145 // *************************
1146 // NUKE
1147 // *************************
1148
1149 #ifdef INCLUDE_NUKE
1150 #define NUKE_DELAY 4
1151 #define NUKE_TIME_TO_LIVE 6
1152 //#define NUKE_TIME_TO_LIVE 40
1153 #define NUKE_RADIUS 512
1154 #define NUKE_DAMAGE 400
1155 #define NUKE_QUAKE_TIME 3
1156 #define NUKE_QUAKE_STRENGTH 100
1157
Nuke_Quake(edict_t * self)1158 void Nuke_Quake (edict_t *self)
1159 {
1160 int i;
1161 edict_t *e;
1162
1163 if (self->last_move_time < level.time)
1164 {
1165 gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 0.75, ATTN_NONE, 0);
1166 self->last_move_time = level.time + 0.5;
1167 }
1168
1169 for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
1170 {
1171 if (!e->inuse)
1172 continue;
1173 if (!e->client)
1174 continue;
1175 if (!e->groundentity)
1176 continue;
1177
1178 e->groundentity = NULL;
1179 e->velocity[0] += crandom()* 150;
1180 e->velocity[1] += crandom()* 150;
1181 e->velocity[2] = self->speed * (100.0 / e->mass);
1182 }
1183
1184 if (level.time < self->timestamp)
1185 self->nextthink = level.time + FRAMETIME;
1186 else
1187 G_FreeEdict (self);
1188 }
1189
1190
Nuke_Explode(edict_t * ent)1191 static void Nuke_Explode (edict_t *ent)
1192 {
1193 // vec3_t origin;
1194
1195 // nuke_framenum = level.framenum + 20;
1196
1197 if (ent->teammaster->client)
1198 PlayerNoise(ent->teammaster, ent->s.origin, PNOISE_IMPACT);
1199
1200 T_RadiusNukeDamage(ent, ent->teammaster, ent->dmg, ent, ent->dmg_radius, MOD_NUKE);
1201
1202 // VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
1203 if (ent->dmg > NUKE_DAMAGE)
1204 gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
1205
1206 gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/grenlx1a.wav"), 1, ATTN_NONE, 0);
1207 /*
1208 gi.WriteByte (svc_temp_entity);
1209 if (ent->groundentity)
1210 gi.WriteByte (TE_GRENADE_EXPLOSION);
1211 else
1212 gi.WriteByte (TE_ROCKET_EXPLOSION);
1213 gi.WritePosition (origin);
1214 gi.multicast (ent->s.origin, MULTICAST_PVS);
1215 */
1216
1217 // BecomeExplosion1(ent);
1218
1219 gi.WriteByte (svc_temp_entity);
1220 gi.WriteByte (TE_EXPLOSION1_BIG);
1221 gi.WritePosition (ent->s.origin);
1222 gi.multicast (ent->s.origin, MULTICAST_PVS);
1223
1224 gi.WriteByte (svc_temp_entity);
1225 gi.WriteByte (TE_NUKEBLAST);
1226 gi.WritePosition (ent->s.origin);
1227 gi.multicast (ent->s.origin, MULTICAST_ALL);
1228
1229 // become a quake
1230 ent->svflags |= SVF_NOCLIENT;
1231 ent->noise_index = gi.soundindex ("world/rumble.wav");
1232 ent->think = Nuke_Quake;
1233 ent->speed = NUKE_QUAKE_STRENGTH;
1234 ent->timestamp = level.time + NUKE_QUAKE_TIME;
1235 ent->nextthink = level.time + FRAMETIME;
1236 ent->last_move_time = 0;
1237 }
1238
nuke_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)1239 void nuke_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
1240 {
1241 self->takedamage = DAMAGE_NO;
1242 if ((attacker) && !(strcmp(attacker->classname, "nuke")))
1243 {
1244 // if ((g_showlogic) && (g_showlogic->value))
1245 // gi.dprintf ("nuke nuked by a nuke, not nuking\n");
1246 G_FreeEdict (self);
1247 return;
1248 }
1249 Nuke_Explode(self);
1250 }
1251
Nuke_Think(edict_t * ent)1252 void Nuke_Think(edict_t *ent)
1253 {
1254 float attenuation, default_atten = 1.8;
1255 int damage_multiplier, muzzleflash;
1256
1257 // gi.dprintf ("player range: %2.2f damage radius: %2.2f\n", realrange (ent, ent->teammaster), ent->dmg_radius*2);
1258
1259 damage_multiplier = ent->dmg/NUKE_DAMAGE;
1260 switch (damage_multiplier)
1261 {
1262 case 1:
1263 attenuation = default_atten/1.4;
1264 muzzleflash = MZ_NUKE1;
1265 break;
1266 case 2:
1267 attenuation = default_atten/2.0;
1268 muzzleflash = MZ_NUKE2;
1269 break;
1270 case 4:
1271 attenuation = default_atten/3.0;
1272 muzzleflash = MZ_NUKE4;
1273 break;
1274 case 8:
1275 attenuation = default_atten/5.0;
1276 muzzleflash = MZ_NUKE8;
1277 break;
1278 default:
1279 // if ((g_showlogic) && (g_showlogic->value))
1280 // gi.dprintf ("default attenuation used for nuke!\n");
1281 attenuation = default_atten;
1282 muzzleflash = MZ_NUKE1;
1283 break;
1284 }
1285
1286 if(ent->wait < level.time)
1287 Nuke_Explode(ent);
1288 else if (level.time >= (ent->wait - NUKE_TIME_TO_LIVE))
1289 {
1290 ent->s.frame++;
1291 // if ((g_showlogic) && (g_showlogic->value))
1292 // gi.dprintf ("nuke frame %d\n", ent->s.frame);
1293 if(ent->s.frame > 11)
1294 ent->s.frame = 6;
1295
1296 if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA))
1297 {
1298 Nuke_Explode (ent);
1299 return;
1300 }
1301
1302 ent->think = Nuke_Think;
1303 ent->nextthink = level.time + 0.1;
1304 ent->health = 1;
1305 ent->owner = NULL;
1306
1307 gi.WriteByte (svc_muzzleflash);
1308 gi.WriteShort (ent-g_edicts);
1309 gi.WriteByte (muzzleflash);
1310 gi.multicast (ent->s.origin, MULTICAST_PVS);
1311
1312 if (ent->timestamp <= level.time)
1313 {
1314 /* gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn.wav"), 1, ATTN_NORM, 0);
1315 ent->timestamp += 10.0;
1316 }
1317 */
1318
1319 if ((ent->wait - level.time) <= (NUKE_TIME_TO_LIVE/2.0))
1320 {
1321 // ent->s.sound = gi.soundindex ("weapons/nukewarn.wav");
1322 // gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
1323 gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0);
1324 // gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
1325 // gi.dprintf ("time %2.2f\n", ent->wait-level.time);
1326 ent->timestamp = level.time + 0.3;
1327 }
1328 else
1329 {
1330 gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0);
1331 // gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
1332 ent->timestamp = level.time + 0.5;
1333 // gi.dprintf ("time %2.2f\n", ent->wait-level.time);
1334 }
1335 }
1336 }
1337 else
1338 {
1339 if (ent->timestamp <= level.time)
1340 {
1341 gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0);
1342 // gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
1343 // gi.dprintf ("time %2.2f\n", ent->wait-level.time);
1344 ent->timestamp = level.time + 1.0;
1345 }
1346 ent->nextthink = level.time + FRAMETIME;
1347 }
1348 }
1349
nuke_bounce(edict_t * ent,edict_t * other,cplane_t * plane,csurface_t * surf)1350 void nuke_bounce (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
1351 {
1352 if (random() > 0.5)
1353 gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
1354 else
1355 gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
1356 }
1357
1358
1359 extern byte P_DamageModifier(edict_t *ent);
1360
fire_nuke(edict_t * self,vec3_t start,vec3_t aimdir,int speed)1361 void fire_nuke (edict_t *self, vec3_t start, vec3_t aimdir, int speed)
1362 {
1363 edict_t *nuke;
1364 vec3_t dir;
1365 vec3_t forward, right, up;
1366 int damage_modifier;
1367
1368 damage_modifier = (int) P_DamageModifier (self);
1369
1370 vectoangles2 (aimdir, dir);
1371 AngleVectors (dir, forward, right, up);
1372
1373 nuke = G_Spawn();
1374 VectorCopy (start, nuke->s.origin);
1375 VectorScale (aimdir, speed, nuke->velocity);
1376
1377 VectorMA (nuke->velocity, 200 + crandom() * 10.0, up, nuke->velocity);
1378 VectorMA (nuke->velocity, crandom() * 10.0, right, nuke->velocity);
1379 VectorClear (nuke->avelocity);
1380 VectorClear (nuke->s.angles);
1381 nuke->movetype = MOVETYPE_BOUNCE;
1382 nuke->clipmask = MASK_SHOT;
1383 nuke->solid = SOLID_BBOX;
1384 nuke->s.effects |= EF_GRENADE;
1385 nuke->s.renderfx |= RF_IR_VISIBLE;
1386 VectorSet (nuke->mins, -8, -8, 0);
1387 VectorSet (nuke->maxs, 8, 8, 16);
1388 nuke->s.modelindex = gi.modelindex ("models/weapons/g_nuke/tris.md2");
1389 nuke->owner = self;
1390 nuke->teammaster = self;
1391 nuke->nextthink = level.time + FRAMETIME;
1392 nuke->wait = level.time + NUKE_DELAY + NUKE_TIME_TO_LIVE;
1393 nuke->think = Nuke_Think;
1394 nuke->touch = nuke_bounce;
1395
1396 nuke->health = 10000;
1397 nuke->takedamage = DAMAGE_YES;
1398 nuke->svflags |= SVF_DAMAGEABLE;
1399 nuke->dmg = NUKE_DAMAGE * damage_modifier;
1400 if (damage_modifier == 1)
1401 nuke->dmg_radius = NUKE_RADIUS;
1402 else
1403 nuke->dmg_radius = NUKE_RADIUS + NUKE_RADIUS*(0.25*(float)damage_modifier);
1404 // this yields 1.0, 1.5, 2.0, 3.0 times radius
1405
1406 // if ((g_showlogic) && (g_showlogic->value))
1407 // gi.dprintf ("nuke modifier = %d, damage = %d, radius = %f\n", damage_modifier, nuke->dmg, nuke->dmg_radius);
1408
1409 nuke->classname = "nuke";
1410 nuke->die = nuke_die;
1411
1412 gi.linkentity (nuke);
1413 }
1414 #endif
1415
1416 // *************************
1417 // TESLA
1418 // *************************
1419
1420 #ifdef INCLUDE_TESLA
1421 #define TESLA_TIME_TO_LIVE 30
1422 #define TESLA_DAMAGE_RADIUS 128
1423 #define TESLA_DAMAGE 3 // 3
1424 #define TESLA_KNOCKBACK 8
1425
1426 #define TESLA_ACTIVATE_TIME 3
1427
1428 #define TESLA_EXPLOSION_DAMAGE_MULT 50 // this is the amount the damage is multiplied by for underwater explosions
1429 #define TESLA_EXPLOSION_RADIUS 200
1430
tesla_remove(edict_t * self)1431 void tesla_remove (edict_t *self)
1432 {
1433 edict_t *cur, *next;
1434
1435 self->takedamage = DAMAGE_NO;
1436 if(self->teamchain)
1437 {
1438 cur = self->teamchain;
1439 while(cur)
1440 {
1441 next = cur->teamchain;
1442 G_FreeEdict ( cur );
1443 cur = next;
1444 }
1445 }
1446 else if (self->air_finished)
1447 gi.dprintf ("tesla without a field!\n");
1448
1449 self->owner = self->teammaster; // Going away, set the owner correctly.
1450 // PGM - grenade explode does damage to self->enemy
1451 self->enemy = NULL;
1452
1453 // play quad sound if quadded and an underwater explosion
1454 if ((self->dmg_radius) && (self->dmg > (TESLA_DAMAGE*TESLA_EXPLOSION_DAMAGE_MULT)))
1455 gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
1456
1457 Grenade_Explode(self);
1458 }
1459
tesla_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)1460 void tesla_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
1461 {
1462 // gi.dprintf("tesla killed\n");
1463 tesla_remove(self);
1464 }
1465
tesla_blow(edict_t * self)1466 void tesla_blow (edict_t *self)
1467 {
1468 // T_RadiusDamage(self, self->owner, TESLA_EXPLOSION_DAMAGE, NULL, TESLA_EXPLOSION_RADIUS, MOD_TESLA);
1469 self->dmg = self->dmg * TESLA_EXPLOSION_DAMAGE_MULT;
1470 self->dmg_radius = TESLA_EXPLOSION_RADIUS;
1471 tesla_remove(self);
1472 }
1473
1474
tesla_zap(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)1475 void tesla_zap (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
1476 {
1477 }
1478
tesla_think_active(edict_t * self)1479 void tesla_think_active (edict_t *self)
1480 {
1481 int i,num;
1482 edict_t *touch[MAX_EDICTS], *hit;
1483 vec3_t dir, start;
1484 trace_t tr;
1485
1486 if(level.time > self->air_finished)
1487 {
1488 tesla_remove(self);
1489 return;
1490 }
1491
1492 VectorCopy(self->s.origin, start);
1493 start[2] += 16;
1494
1495 num = gi.BoxEdicts(self->teamchain->absmin, self->teamchain->absmax, touch, MAX_EDICTS, AREA_SOLID);
1496 for(i=0;i<num;i++)
1497 {
1498 // if the tesla died while zapping things, stop zapping.
1499 if(!(self->inuse))
1500 break;
1501
1502 hit=touch[i];
1503 if(!hit->inuse)
1504 continue;
1505 if(hit == self)
1506 continue;
1507 if(hit->health < 1)
1508 continue;
1509 // don't hit clients in single-player or coop
1510 if(hit->client)
1511 if (coop->value || !deathmatch->value)
1512 continue;
1513 if(!(hit->svflags & (SVF_MONSTER | SVF_DAMAGEABLE)) && !hit->client)
1514 continue;
1515
1516 tr = gi.trace(start, vec3_origin, vec3_origin, hit->s.origin, self, MASK_SHOT);
1517 if(tr.fraction==1 || tr.ent==hit)// || tr.ent->client || (tr.ent->svflags & (SVF_MONSTER | SVF_DAMAGEABLE)))
1518 {
1519 VectorSubtract(hit->s.origin, start, dir);
1520
1521 // PMM - play quad sound if it's above the "normal" damage
1522 if (self->dmg > TESLA_DAMAGE)
1523 gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
1524
1525 // PGM - don't do knockback to walking monsters
1526 if((hit->svflags & SVF_MONSTER) && !(hit->flags & (FL_FLY|FL_SWIM)))
1527 T_Damage (hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal,
1528 self->dmg, 0, 0, MOD_TESLA);
1529 else
1530 T_Damage (hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal,
1531 self->dmg, TESLA_KNOCKBACK, 0, MOD_TESLA);
1532
1533 gi.WriteByte (svc_temp_entity);
1534 gi.WriteByte (TE_LIGHTNING);
1535 gi.WriteShort (hit - g_edicts); // destination entity
1536 gi.WriteShort (self - g_edicts); // source entity
1537 gi.WritePosition (tr.endpos);
1538 gi.WritePosition (start);
1539 gi.multicast (start, MULTICAST_PVS);
1540 }
1541 }
1542
1543 if(self->inuse)
1544 {
1545 self->think = tesla_think_active;
1546 self->nextthink = level.time + FRAMETIME;
1547 }
1548 }
1549
tesla_activate(edict_t * self)1550 void tesla_activate (edict_t *self)
1551 {
1552 edict_t *trigger;
1553 edict_t *search;
1554
1555 if (gi.pointcontents (self->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WATER))
1556 {
1557 tesla_blow (self);
1558 return;
1559 }
1560
1561 // only check for spawn points in deathmatch
1562 if (deathmatch->value)
1563 {
1564 search = NULL;
1565 while ((search = findradius(search, self->s.origin, 1.5*TESLA_DAMAGE_RADIUS)) != NULL)
1566 {
1567 //if (!search->takedamage)
1568 // continue;
1569 // if it's a monster or player with health > 0
1570 // or it's a deathmatch start point
1571 // and we can see it
1572 // blow up
1573 if(search->classname)
1574 {
1575 if ( ( (!strcmp(search->classname, "info_player_deathmatch"))
1576 || (!strcmp(search->classname, "info_player_start"))
1577 || (!strcmp(search->classname, "info_player_coop"))
1578 || (!strcmp(search->classname, "misc_teleporter_dest"))
1579 )
1580 && (visible (search, self))
1581 )
1582 {
1583 // if ((g_showlogic) && (g_showlogic->value))
1584 // gi.dprintf ("Tesla to close to %s, removing!\n", search->classname);
1585 tesla_remove (self);
1586 return;
1587 }
1588 }
1589 }
1590 }
1591
1592 trigger = G_Spawn();
1593 // if (trigger->nextthink)
1594 // {
1595 // if ((g_showlogic) && (g_showlogic->value))
1596 // gi.dprintf ("tesla_activate: fixing nextthink\n");
1597 // trigger->nextthink = 0;
1598 // }
1599 VectorCopy (self->s.origin, trigger->s.origin);
1600 VectorSet (trigger->mins, -TESLA_DAMAGE_RADIUS, -TESLA_DAMAGE_RADIUS, self->mins[2]);
1601 VectorSet (trigger->maxs, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS);
1602 trigger->movetype = MOVETYPE_NONE;
1603 trigger->solid = SOLID_TRIGGER;
1604 trigger->owner = self;
1605 trigger->touch = tesla_zap;
1606 trigger->classname = "tesla trigger";
1607 // doesn't need to be marked as a teamslave since the move code for bounce looks for teamchains
1608 gi.linkentity (trigger);
1609
1610 VectorClear (self->s.angles);
1611 // clear the owner if in deathmatch
1612 if (deathmatch->value)
1613 self->owner = NULL;
1614 self->teamchain = trigger;
1615 self->think = tesla_think_active;
1616 self->nextthink = level.time + FRAMETIME;
1617 self->air_finished = level.time + TESLA_TIME_TO_LIVE;
1618 }
1619
tesla_think(edict_t * ent)1620 void tesla_think (edict_t *ent)
1621 {
1622 if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA))
1623 {
1624 tesla_remove (ent);
1625 return;
1626 }
1627 VectorClear (ent->s.angles);
1628
1629 if(!(ent->s.frame))
1630 gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/teslaopen.wav"), 1, ATTN_NORM, 0);
1631
1632 ent->s.frame++;
1633 if(ent->s.frame > 14)
1634 {
1635 ent->s.frame = 14;
1636 ent->think = tesla_activate;
1637 ent->nextthink = level.time + 0.1;
1638 }
1639 else
1640 {
1641 if(ent->s.frame > 9)
1642 {
1643 if(ent->s.frame == 10)
1644 {
1645 if (ent->owner && ent->owner->client)
1646 {
1647 PlayerNoise(ent->owner, ent->s.origin, PNOISE_WEAPON); // PGM
1648 }
1649 ent->s.skinnum = 1;
1650 }
1651 else if(ent->s.frame == 12)
1652 ent->s.skinnum = 2;
1653 else if(ent->s.frame == 14)
1654 ent->s.skinnum = 3;
1655 }
1656 ent->think = tesla_think;
1657 ent->nextthink = level.time + 0.1;
1658 }
1659 }
1660
tesla_lava(edict_t * ent,edict_t * other,cplane_t * plane,csurface_t * surf)1661 void tesla_lava (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
1662 {
1663 vec3_t land_point;
1664
1665 if (plane->normal)
1666 {
1667 VectorMA (ent->s.origin, -20.0, plane->normal, land_point);
1668 if (gi.pointcontents (land_point) & (CONTENTS_SLIME|CONTENTS_LAVA))
1669 {
1670 tesla_blow (ent);
1671 return;
1672 }
1673 }
1674 if (random() > 0.5)
1675 gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
1676 else
1677 gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
1678 }
1679
fire_tesla(edict_t * self,vec3_t start,vec3_t aimdir,int damage_multiplier,int speed)1680 void fire_tesla (edict_t *self, vec3_t start, vec3_t aimdir, int damage_multiplier, int speed)
1681 {
1682 edict_t *tesla;
1683 vec3_t dir;
1684 vec3_t forward, right, up;
1685
1686 vectoangles2 (aimdir, dir);
1687 AngleVectors (dir, forward, right, up);
1688
1689 tesla = G_Spawn();
1690 VectorCopy (start, tesla->s.origin);
1691 VectorScale (aimdir, speed, tesla->velocity);
1692 VectorMA (tesla->velocity, 200 + crandom() * 10.0, up, tesla->velocity);
1693 VectorMA (tesla->velocity, crandom() * 10.0, right, tesla->velocity);
1694 // VectorCopy (dir, tesla->s.angles);
1695 VectorClear (tesla->s.angles);
1696 tesla->movetype = MOVETYPE_BOUNCE;
1697 tesla->solid = SOLID_BBOX;
1698 tesla->s.effects |= EF_GRENADE;
1699 tesla->s.renderfx |= RF_IR_VISIBLE;
1700 // VectorClear (tesla->mins);
1701 // VectorClear (tesla->maxs);
1702 VectorSet (tesla->mins, -12, -12, 0);
1703 VectorSet (tesla->maxs, 12, 12, 20);
1704 tesla->s.modelindex = gi.modelindex ("models/weapons/g_tesla/tris.md2");
1705
1706 tesla->owner = self; // PGM - we don't want it owned by self YET.
1707 tesla->teammaster = self;
1708
1709 tesla->wait = level.time + TESLA_TIME_TO_LIVE;
1710 tesla->think = tesla_think;
1711 tesla->nextthink = level.time + TESLA_ACTIVATE_TIME;
1712
1713 // blow up on contact with lava & slime code
1714 tesla->touch = tesla_lava;
1715
1716 if(deathmatch->value)
1717 // PMM - lowered from 50 - 7/29/1998
1718 tesla->health = 20;
1719 else
1720 tesla->health = 30; // FIXME - change depending on skill?
1721
1722 tesla->takedamage = DAMAGE_YES;
1723 tesla->die = tesla_die;
1724 tesla->dmg = TESLA_DAMAGE*damage_multiplier;
1725 // tesla->dmg = 0;
1726 tesla->classname = "tesla";
1727 tesla->svflags |= SVF_DAMAGEABLE;
1728 tesla->clipmask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA;
1729 tesla->flags |= FL_MECHANICAL;
1730
1731 gi.linkentity (tesla);
1732 }
1733 #endif
1734
1735 // *************************
1736 // HEATBEAM
1737 // *************************
1738
1739 #ifdef INCLUDE_BEAMS
fire_beams(edict_t * self,vec3_t start,vec3_t aimdir,vec3_t offset,int damage,int kick,int te_beam,int te_impact,int mod)1740 static void fire_beams (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, int te_beam, int te_impact, int mod)
1741 {
1742 trace_t tr;
1743 vec3_t dir;
1744 vec3_t forward, right, up;
1745 vec3_t end;
1746 vec3_t water_start, endpoint;
1747 qboolean water = false, underwater = false;
1748 int content_mask = MASK_SHOT | MASK_WATER;
1749 vec3_t beam_endpt;
1750
1751 // tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT);
1752 // if (!(tr.fraction < 1.0))
1753 // {
1754 vectoangles2 (aimdir, dir);
1755 AngleVectors (dir, forward, right, up);
1756
1757 VectorMA (start, 8192, forward, end);
1758
1759 if (gi.pointcontents (start) & MASK_WATER)
1760 {
1761 // gi.dprintf ("Heat beam under water\n");
1762 underwater = true;
1763 VectorCopy (start, water_start);
1764 content_mask &= ~MASK_WATER;
1765 }
1766
1767 tr = gi.trace (start, NULL, NULL, end, self, content_mask);
1768
1769 // see if we hit water
1770 if (tr.contents & MASK_WATER)
1771 {
1772 water = true;
1773 VectorCopy (tr.endpos, water_start);
1774
1775 if (!VectorCompare (start, tr.endpos))
1776 {
1777 gi.WriteByte (svc_temp_entity);
1778 gi.WriteByte (TE_HEATBEAM_SPARKS);
1779 // gi.WriteByte (50);
1780 gi.WritePosition (water_start);
1781 gi.WriteDir (tr.plane.normal);
1782 // gi.WriteByte (8);
1783 // gi.WriteShort (60);
1784 gi.multicast (tr.endpos, MULTICAST_PVS);
1785 }
1786 // re-trace ignoring water this time
1787 tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT);
1788 }
1789 VectorCopy (tr.endpos, endpoint);
1790 // }
1791
1792 // halve the damage if target underwater
1793 if (water)
1794 {
1795 damage = damage /2;
1796 }
1797
1798 // send gun puff / flash
1799 if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
1800 {
1801 if (tr.fraction < 1.0)
1802 {
1803 if (tr.ent->takedamage)
1804 {
1805 T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_ENERGY, mod);
1806 }
1807 else
1808 {
1809 if ((!water) && (strncmp (tr.surface->name, "sky", 3)))
1810 {
1811 // This is the truncated steam entry - uses 1+1+2 extra bytes of data
1812 gi.WriteByte (svc_temp_entity);
1813 gi.WriteByte (TE_HEATBEAM_STEAM);
1814 // gi.WriteByte (20);
1815 gi.WritePosition (tr.endpos);
1816 gi.WriteDir (tr.plane.normal);
1817 // gi.WriteByte (0xe0);
1818 // gi.WriteShort (60);
1819 gi.multicast (tr.endpos, MULTICAST_PVS);
1820
1821 if (self->client)
1822 PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
1823 }
1824 }
1825 }
1826 }
1827
1828 // if went through water, determine where the end and make a bubble trail
1829 if ((water) || (underwater))
1830 {
1831 vec3_t pos;
1832
1833 VectorSubtract (tr.endpos, water_start, dir);
1834 VectorNormalize (dir);
1835 VectorMA (tr.endpos, -2, dir, pos);
1836 if (gi.pointcontents (pos) & MASK_WATER)
1837 VectorCopy (pos, tr.endpos);
1838 else
1839 tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
1840
1841 VectorAdd (water_start, tr.endpos, pos);
1842 VectorScale (pos, 0.5, pos);
1843
1844 gi.WriteByte (svc_temp_entity);
1845 gi.WriteByte (TE_BUBBLETRAIL2);
1846 // gi.WriteByte (8);
1847 gi.WritePosition (water_start);
1848 gi.WritePosition (tr.endpos);
1849 gi.multicast (pos, MULTICAST_PVS);
1850 }
1851
1852 if ((!underwater) && (!water))
1853 {
1854 VectorCopy (tr.endpos, beam_endpt);
1855 }
1856 else
1857 {
1858 VectorCopy (endpoint, beam_endpt);
1859 }
1860
1861 gi.WriteByte (svc_temp_entity);
1862 gi.WriteByte (te_beam);
1863 gi.WriteShort (self - g_edicts);
1864 gi.WritePosition (start);
1865 gi.WritePosition (beam_endpt);
1866 gi.multicast (self->s.origin, MULTICAST_ALL);
1867
1868 }
1869
1870
1871 /*
1872 =================
1873 fire_heat
1874
1875 Fires a single heat beam. Zap.
1876 =================
1877 */
fire_heat(edict_t * self,vec3_t start,vec3_t aimdir,vec3_t offset,int damage,int kick,qboolean monster)1878 void fire_heat (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, qboolean monster)
1879 {
1880 if (monster)
1881 fire_beams (self, start, aimdir, offset, damage, kick, TE_MONSTER_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM);
1882 else
1883 fire_beams (self, start, aimdir, offset, damage, kick, TE_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM);
1884 }
1885
1886 #endif
1887
1888
1889 // *************************
1890 // BLASTER 2
1891 // *************************
1892
1893 /*
1894 =================
1895 fire_blaster2
1896
1897 Fires a single green blaster bolt. Used by monsters, generally.
1898 =================
1899 */
blaster2_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)1900 void blaster2_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
1901 {
1902 int mod;
1903 int damagestat;
1904
1905 if (other == self->owner)
1906 return;
1907
1908 if (surf && (surf->flags & SURF_SKY))
1909 {
1910 G_FreeEdict (self);
1911 return;
1912 }
1913
1914 if (self->owner && self->owner->client)
1915 PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
1916
1917 if (other->takedamage)
1918 {
1919 // the only time players will be firing blaster2 bolts will be from the
1920 // defender sphere.
1921 if(self->owner->client)
1922 mod = MOD_DEFENDER_SPHERE;
1923 else
1924 mod = MOD_BLASTER2;
1925
1926 if (self->owner)
1927 {
1928 damagestat = self->owner->takedamage;
1929 self->owner->takedamage = DAMAGE_NO;
1930 if (self->dmg >= 5)
1931 T_RadiusDamage(self, self->owner, self->dmg*3, other, self->dmg_radius, 0);
1932 T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod);
1933 self->owner->takedamage = damagestat;
1934 }
1935 else
1936 {
1937 if (self->dmg >= 5)
1938 T_RadiusDamage(self, self->owner, self->dmg*3, other, self->dmg_radius, 0);
1939 T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod);
1940 }
1941 }
1942 else
1943 {
1944 //PMM - yeowch this will get expensive
1945 if (self->dmg >= 5)
1946 T_RadiusDamage(self, self->owner, self->dmg*3, self->owner, self->dmg_radius, 0);
1947
1948 gi.WriteByte (svc_temp_entity);
1949 gi.WriteByte (TE_BLASTER2);
1950 gi.WritePosition (self->s.origin);
1951 if (!plane)
1952 gi.WriteDir (vec3_origin);
1953 else
1954 gi.WriteDir (plane->normal);
1955 gi.multicast (self->s.origin, MULTICAST_PVS);
1956 }
1957
1958 G_FreeEdict (self);
1959 }
1960
fire_blaster2(edict_t * self,vec3_t start,vec3_t dir,int damage,int speed,int effect,qboolean hyper)1961 void fire_blaster2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper)
1962 {
1963 edict_t *bolt;
1964 trace_t tr;
1965
1966 VectorNormalize (dir);
1967
1968 bolt = G_Spawn();
1969 VectorCopy (start, bolt->s.origin);
1970 VectorCopy (start, bolt->s.old_origin);
1971 vectoangles2 (dir, bolt->s.angles);
1972 VectorScale (dir, speed, bolt->velocity);
1973 bolt->movetype = MOVETYPE_FLYMISSILE;
1974 bolt->clipmask = MASK_SHOT;
1975 bolt->solid = SOLID_BBOX;
1976 bolt->s.effects |= effect;
1977 VectorClear (bolt->mins);
1978 VectorClear (bolt->maxs);
1979
1980 if (effect)
1981 bolt->s.effects |= EF_TRACKER;
1982 bolt->dmg_radius = 128;
1983 bolt->s.modelindex = gi.modelindex ("models/proj/laser2/tris.md2");
1984 bolt->touch = blaster2_touch;
1985
1986 bolt->owner = self;
1987 bolt->nextthink = level.time + 2;
1988 bolt->think = G_FreeEdict;
1989 bolt->dmg = damage;
1990 bolt->classname = "bolt";
1991 gi.linkentity (bolt);
1992
1993 if (self->client)
1994 check_dodge (self, bolt->s.origin, dir, speed);
1995
1996 tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
1997 if (tr.fraction < 1.0)
1998 {
1999 VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
2000 bolt->touch (bolt, tr.ent, NULL, NULL);
2001 }
2002 }
2003
2004 // *************************
2005 // tracker
2006 // *************************
2007
2008 /*
2009 void tracker_boom_think (edict_t *self)
2010 {
2011 self->s.frame--;
2012 if(self->s.frame < 0)
2013 G_FreeEdict(self);
2014 else
2015 self->nextthink = level.time + 0.1;
2016 }
2017
2018 void tracker_boom_spawn (vec3_t origin)
2019 {
2020 edict_t *boom;
2021
2022 boom = G_Spawn();
2023 VectorCopy (origin, boom->s.origin);
2024 boom->s.modelindex = gi.modelindex ("models/items/spawngro/tris.md2");
2025 boom->s.skinnum = 1;
2026 boom->s.frame = 2;
2027 boom->classname = "tracker boom";
2028 gi.linkentity (boom);
2029
2030 boom->think = tracker_boom_think;
2031 boom->nextthink = level.time + 0.1;
2032 //PMM
2033 // boom->s.renderfx |= RF_TRANSLUCENT;
2034 boom->s.effects |= EF_SPHERETRANS;
2035 //pmm
2036 }
2037 */
2038
2039 #define TRACKER_DAMAGE_FLAGS (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY | DAMAGE_NO_KNOCKBACK)
2040 #define TRACKER_IMPACT_FLAGS (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY)
2041
2042 #define TRACKER_DAMAGE_TIME 0.5 // seconds
2043
tracker_pain_daemon_think(edict_t * self)2044 void tracker_pain_daemon_think (edict_t *self)
2045 {
2046 static vec3_t pain_normal = { 0, 0, 1 };
2047 int hurt;
2048
2049 if(!self->inuse)
2050 return;
2051
2052 if((level.time - self->timestamp) > TRACKER_DAMAGE_TIME)
2053 {
2054 if(!self->enemy->client)
2055 self->enemy->s.effects &= ~EF_TRACKERTRAIL;
2056 G_FreeEdict (self);
2057 }
2058 else
2059 {
2060 if(self->enemy->health > 0)
2061 {
2062 // gi.dprintf("ouch %x\n", self);
2063 T_Damage (self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin, pain_normal,
2064 self->dmg, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER);
2065
2066 // if we kill the player, we'll be removed.
2067 if(self->inuse)
2068 {
2069 // if we killed a monster, gib them.
2070 if (self->enemy->health < 1)
2071 {
2072 if(self->enemy->gib_health)
2073 hurt = - self->enemy->gib_health;
2074 else
2075 hurt = 500;
2076
2077 // gi.dprintf("non-player killed. ensuring gib! %d\n", hurt);
2078 T_Damage (self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin,
2079 pain_normal, hurt, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER);
2080 }
2081
2082 if(self->enemy->client)
2083 self->enemy->client->tracker_pain_framenum = level.framenum + 1;
2084 else
2085 self->enemy->s.effects |= EF_TRACKERTRAIL;
2086
2087 self->nextthink = level.time + FRAMETIME;
2088 }
2089 }
2090 else
2091 {
2092 if(!self->enemy->client)
2093 self->enemy->s.effects &= ~EF_TRACKERTRAIL;
2094 G_FreeEdict (self);
2095 }
2096 }
2097 }
2098
tracker_pain_daemon_spawn(edict_t * owner,edict_t * enemy,int damage)2099 void tracker_pain_daemon_spawn (edict_t *owner, edict_t *enemy, int damage)
2100 {
2101 edict_t *daemon;
2102
2103 if(enemy == NULL)
2104 return;
2105
2106 daemon = G_Spawn();
2107 daemon->classname = "pain daemon";
2108 daemon->think = tracker_pain_daemon_think;
2109 daemon->nextthink = level.time + FRAMETIME;
2110 daemon->timestamp = level.time;
2111 daemon->owner = owner;
2112 daemon->enemy = enemy;
2113 daemon->dmg = damage;
2114 }
2115
tracker_explode(edict_t * self,cplane_t * plane)2116 void tracker_explode (edict_t *self, cplane_t *plane)
2117 {
2118 vec3_t dir;
2119
2120 if(!plane)
2121 VectorClear (dir);
2122 else
2123 VectorScale (plane->normal, 256, dir);
2124
2125 gi.WriteByte (svc_temp_entity);
2126 gi.WriteByte (TE_TRACKER_EXPLOSION);
2127 gi.WritePosition (self->s.origin);
2128 gi.multicast (self->s.origin, MULTICAST_PVS);
2129
2130 // gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/disrupthit.wav"), 1, ATTN_NORM, 0);
2131 // tracker_boom_spawn(self->s.origin);
2132
2133 G_FreeEdict (self);
2134 }
2135
tracker_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)2136 void tracker_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
2137 {
2138 float damagetime;
2139
2140 if (other == self->owner)
2141 return;
2142
2143 if (surf && (surf->flags & SURF_SKY))
2144 {
2145 G_FreeEdict (self);
2146 return;
2147 }
2148
2149 if (self->client)
2150 PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
2151
2152 if (other->takedamage)
2153 {
2154 if((other->svflags & SVF_MONSTER) || other->client)
2155 {
2156 if(other->health > 0) // knockback only for living creatures
2157 {
2158 // PMM - kickback was times 4 .. reduced to 3
2159 // now this does no damage, just knockback
2160 T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
2161 /* self->dmg */ 0, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER);
2162
2163 if (!(other->flags & (FL_FLY|FL_SWIM)))
2164 other->velocity[2] += 140;
2165
2166 damagetime = ((float)self->dmg)*FRAMETIME;
2167 damagetime = damagetime / TRACKER_DAMAGE_TIME;
2168 // gi.dprintf ("damage is %f\n", damagetime);
2169
2170 tracker_pain_daemon_spawn (self->owner, other, (int)damagetime);
2171 }
2172 else // lots of damage (almost autogib) for dead bodies
2173 {
2174 T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
2175 self->dmg*4, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER);
2176 }
2177 }
2178 else // full damage in one shot for inanimate objects
2179 {
2180 T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
2181 self->dmg, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER);
2182 }
2183 }
2184
2185 tracker_explode (self, plane);
2186 return;
2187 }
2188
tracker_fly(edict_t * self)2189 void tracker_fly (edict_t *self)
2190 {
2191 vec3_t dest;
2192 vec3_t dir;
2193 vec3_t center;
2194
2195 if ((!self->enemy) || (!self->enemy->inuse) || (self->enemy->health < 1))
2196 {
2197 tracker_explode (self, NULL);
2198 return;
2199 }
2200 /*
2201 VectorCopy (self->enemy->s.origin, dest);
2202 if(self->enemy->client)
2203 dest[2] += self->enemy->viewheight;
2204 */
2205 // PMM - try to hunt for center of enemy, if possible and not client
2206 if(self->enemy->client)
2207 {
2208 VectorCopy (self->enemy->s.origin, dest);
2209 dest[2] += self->enemy->viewheight;
2210 }
2211 // paranoia
2212 else if (VectorCompare(self->enemy->absmin, vec3_origin) || VectorCompare(self->enemy->absmax, vec3_origin))
2213 {
2214 VectorCopy (self->enemy->s.origin, dest);
2215 }
2216 else
2217 {
2218 VectorMA (vec3_origin, 0.5, self->enemy->absmin, center);
2219 VectorMA (center, 0.5, self->enemy->absmax, center);
2220 VectorCopy (center, dest);
2221 }
2222
2223 VectorSubtract (dest, self->s.origin, dir);
2224 VectorNormalize (dir);
2225 vectoangles2 (dir, self->s.angles);
2226 VectorScale (dir, self->speed, self->velocity);
2227 VectorCopy(dest, self->monsterinfo.saved_goal);
2228
2229 self->nextthink = level.time + 0.1;
2230 }
2231
fire_tracker(edict_t * self,vec3_t start,vec3_t dir,int damage,int speed,edict_t * enemy)2232 void fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy)
2233 {
2234 edict_t *bolt;
2235 trace_t tr;
2236
2237 VectorNormalize (dir);
2238
2239 bolt = G_Spawn();
2240 VectorCopy (start, bolt->s.origin);
2241 VectorCopy (start, bolt->s.old_origin);
2242 vectoangles2 (dir, bolt->s.angles);
2243 VectorScale (dir, speed, bolt->velocity);
2244 bolt->movetype = MOVETYPE_FLYMISSILE;
2245 bolt->clipmask = MASK_SHOT;
2246 bolt->solid = SOLID_BBOX;
2247 bolt->speed = speed;
2248 bolt->s.effects = EF_TRACKER;
2249 bolt->s.sound = gi.soundindex ("weapons/disrupt.wav");
2250 VectorClear (bolt->mins);
2251 VectorClear (bolt->maxs);
2252
2253 bolt->s.modelindex = gi.modelindex ("models/proj/disintegrator/tris.md2");
2254 bolt->touch = tracker_touch;
2255 bolt->enemy = enemy;
2256 bolt->owner = self;
2257 bolt->dmg = damage;
2258 bolt->classname = "tracker";
2259 gi.linkentity (bolt);
2260
2261 if(enemy)
2262 {
2263 bolt->nextthink = level.time + 0.1;
2264 bolt->think = tracker_fly;
2265 }
2266 else
2267 {
2268 bolt->nextthink = level.time + 10;
2269 bolt->think = G_FreeEdict;
2270 }
2271
2272 if (self->client)
2273 check_dodge (self, bolt->s.origin, dir, speed);
2274
2275 tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
2276 if (tr.fraction < 1.0)
2277 {
2278 VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
2279 bolt->touch (bolt, tr.ent, NULL, NULL);
2280 }
2281 }
2282