1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 //
21 // cg_tempents.c
22 //
23
24 #include "cg_local.h"
25
26 // explo rattles
27 #define MAX_TENT_EXPLORATTLES 32
28 float cgExploRattles[MAX_TENT_EXPLORATTLES];
29
30 // beams
31 #define MAX_TENT_BEAMS 32
32 typedef struct teBeam_s {
33 int entity;
34 int dest_entity;
35 struct refModel_s *model;
36 int endtime;
37 vec3_t offset;
38 vec3_t start, end;
39 } teBeam_t;
40 teBeam_t cgBeams[MAX_TENT_BEAMS];
41 teBeam_t cgPlayerBeams[MAX_TENT_BEAMS]; // player-linked beams
42
43 // lasers
44 #define MAX_TENT_LASERS 32
45 typedef struct teLaser_s {
46 refEntity_t ent;
47 int endtime;
48 } teLaser_t;
49 teLaser_t cgLasers[MAX_TENT_LASERS];
50
51 /*
52 =============================================================================
53
54 EXPLOSION SCREEN RATTLES
55
56 =============================================================================
57 */
58
59 /*
60 =================
61 CG_ExploRattle
62 =================
63 */
CG_ExploRattle(vec3_t org,float scale)64 void CG_ExploRattle (vec3_t org, float scale)
65 {
66 int i;
67 float dist, max;
68 vec3_t temp;
69
70 if (!cl_explorattle->intVal)
71 return;
72
73 for (i=0 ; i<MAX_TENT_EXPLORATTLES ; i++) {
74 if (cgExploRattles[i] > 0)
75 continue;
76
77 // calculate distance
78 dist = Vec3DistFast (cg.refDef.viewOrigin, org) * 0.1;
79 max = (20 * scale, 20, 50);
80
81 // lessen the effect when it's behind the view
82 Vec3Subtract (org, cg.refDef.viewOrigin, temp);
83 VectorNormalizef (temp, temp);
84
85 if (DotProduct (temp, cg.refDef.viewAxis[0]) < 0)
86 dist *= 1.25;
87
88 // clamp
89 if ((dist > 0) && (dist < max))
90 cgExploRattles[i] = max - dist;
91
92 break;
93 }
94 }
95
96
97 /*
98 =================
99 CG_AddExploRattles
100 =================
101 */
CG_AddExploRattles(void)102 static void CG_AddExploRattles (void)
103 {
104 int i;
105 float scale;
106
107 if (!cl_explorattle->intVal)
108 return;
109
110 scale = clamp (cl_explorattle_scale->floatVal, 0, 0.95);
111 for (i=0 ; i<MAX_TENT_EXPLORATTLES ; i++) {
112 if (cgExploRattles[i] <= 0)
113 continue;
114
115 cgExploRattles[i] *= scale * scale;
116
117 cg.refDef.viewAngles[0] += cgExploRattles[i] * crand ();
118 cg.refDef.viewAngles[1] += cgExploRattles[i] * crand ();
119 cg.refDef.viewAngles[2] += cgExploRattles[i] * crand ();
120
121 if (cgExploRattles[i] < 0.001)
122 cgExploRattles[i] = -1;
123 }
124 }
125
126
127 /*
128 =================
129 CG_ClearExploRattles
130 =================
131 */
CG_ClearExploRattles(void)132 static void CG_ClearExploRattles (void)
133 {
134 int i;
135
136 for (i=0 ; i<MAX_TENT_EXPLORATTLES ; i++)
137 cgExploRattles[i] = -1;
138 }
139 /*
140 =============================================================================
141
142 BEAM MANAGEMENT
143
144 =============================================================================
145 */
146
147 /*
148 =================
149 CG_ParseBeam
150 =================
151 */
CG_ParseBeam(struct refModel_s * model)152 static int CG_ParseBeam (struct refModel_s *model)
153 {
154 int ent;
155 vec3_t start, end;
156 teBeam_t *b;
157 int i;
158
159 ent = cgi.MSG_ReadShort ();
160
161 cgi.MSG_ReadPos (start);
162 cgi.MSG_ReadPos (end);
163
164 // override any beam with the same entity
165 for (i=0, b=cgBeams ; i<MAX_TENT_BEAMS ; i++, b++) {
166 if (b->entity == ent) {
167 b->entity = ent;
168 b->model = model;
169 b->endtime = cg.realTime + 200;
170 Vec3Copy (start, b->start);
171 Vec3Copy (end, b->end);
172 Vec3Clear (b->offset);
173 return ent;
174 }
175 }
176
177 // find a free beam
178 for (i=0, b=cgBeams ; i<MAX_TENT_BEAMS ; i++, b++) {
179 if (!b->model || (b->endtime < cg.realTime)) {
180 b->entity = ent;
181 b->model = model;
182 b->endtime = cg.realTime + 200;
183 Vec3Copy (start, b->start);
184 Vec3Copy (end, b->end);
185 Vec3Clear (b->offset);
186 return ent;
187 }
188 }
189
190 Com_Printf (PRNT_WARNING, "beam list overflow!\n");
191 return ent;
192 }
193
194
195 /*
196 =================
197 CG_ParseBeam2
198 =================
199 */
CG_ParseBeam2(struct refModel_s * model)200 static int CG_ParseBeam2 (struct refModel_s *model)
201 {
202 int ent;
203 vec3_t start, end, offset;
204 teBeam_t *b;
205 int i;
206
207 ent = cgi.MSG_ReadShort ();
208
209 cgi.MSG_ReadPos (start);
210 cgi.MSG_ReadPos (end);
211 cgi.MSG_ReadPos (offset);
212
213 // override any beam with the same entity
214 for (i=0, b=cgBeams ; i<MAX_TENT_BEAMS ; i++, b++) {
215 if (b->entity == ent) {
216 b->entity = ent;
217 b->model = model;
218 b->endtime = cg.realTime + 200;
219 Vec3Copy (start, b->start);
220 Vec3Copy (end, b->end);
221 Vec3Copy (offset, b->offset);
222 return ent;
223 }
224 }
225
226 // find a free beam
227 for (i=0, b=cgBeams ; i<MAX_TENT_BEAMS ; i++, b++) {
228 if (!b->model || (b->endtime < cg.realTime)) {
229 b->entity = ent;
230 b->model = model;
231 b->endtime = cg.realTime + 200;
232 Vec3Copy (start, b->start);
233 Vec3Copy (end, b->end);
234 Vec3Copy (offset, b->offset);
235 return ent;
236 }
237 }
238
239 Com_Printf (PRNT_WARNING, "beam list overflow!\n");
240 return ent;
241 }
242
243
244 /*
245 =================
246 CG_ParsePlayerBeam
247
248 Adds to the cgPlayerBeams array instead of the cgBeams array
249 =================
250 */
CG_ParsePlayerBeam(struct refModel_s * model)251 static int CG_ParsePlayerBeam (struct refModel_s *model)
252 {
253 int ent;
254 vec3_t start, end, offset;
255 teBeam_t *b;
256 int i;
257
258 ent = cgi.MSG_ReadShort ();
259
260 cgi.MSG_ReadPos (start);
261 cgi.MSG_ReadPos (end);
262
263 if (model == cgMedia.heatBeamModel)
264 Vec3Set (offset, 2, 7, -3);
265 else if (model == cgMedia.monsterHeatBeamModel) {
266 model = cgMedia.heatBeamModel;
267 Vec3Set (offset, 0, 0, 0);
268 }
269 else
270 cgi.MSG_ReadPos (offset);
271
272 // override any beam with the same entity
273 // PMM - For player beams, we only want one per player (entity) so..
274 for (i=0, b=cgPlayerBeams ; i<MAX_TENT_BEAMS ; i++, b++) {
275 if (b->entity == ent) {
276 b->entity = ent;
277 b->model = model;
278 b->endtime = cg.realTime + 200;
279 Vec3Copy (start, b->start);
280 Vec3Copy (end, b->end);
281 Vec3Copy (offset, b->offset);
282 return ent;
283 }
284 }
285
286 // find a free beam
287 for (i=0, b=cgPlayerBeams ; i<MAX_TENT_BEAMS ; i++, b++) {
288 if (!b->model || (b->endtime < cg.realTime)) {
289 b->entity = ent;
290 b->model = model;
291 b->endtime = cg.realTime + 100; // PMM - this needs to be 100 to prevent multiple heatbeams
292 Vec3Copy (start, b->start);
293 Vec3Copy (end, b->end);
294 Vec3Copy (offset, b->offset);
295 return ent;
296 }
297 }
298
299 Com_Printf (PRNT_WARNING, "beam list overflow!\n");
300 return ent;
301 }
302
303
304 /*
305 =================
306 CG_AddBeams
307 =================
308 */
CG_AddBeams(void)309 static void CG_AddBeams (void)
310 {
311 int i, j;
312 float d, yaw, pitch, forward, len, steps;
313 float model_length;
314 teBeam_t *b;
315 vec3_t dist, org;
316 refEntity_t ent;
317 vec3_t angles;
318
319 // update beams
320 for (i=0, b=cgBeams ; i<MAX_TENT_BEAMS ; i++, b++) {
321 if (!b->model || (b->endtime < cg.realTime))
322 continue;
323
324 // if coming from the player, update the start position
325 // entity 0 is the world
326 if (b->entity == cg.playerNum+1) {
327 Vec3Copy (cg.refDef.viewOrigin, b->start);
328 b->start[2] -= 22; // adjust for view height
329 }
330 Vec3Add (b->start, b->offset, org);
331
332 // calculate pitch and yaw
333 Vec3Subtract (b->end, org, dist);
334
335 if (dist[1] == 0 && dist[0] == 0) {
336 yaw = 0;
337 if (dist[2] > 0)
338 pitch = 90;
339 else
340 pitch = 270;
341 }
342 else {
343 // PMM - fixed to correct for pitch of 0
344 if (dist[0])
345 yaw = (atan2 (dist[1], dist[0]) * (180.0f / M_PI));
346 else if (dist[1] > 0)
347 yaw = 90;
348 else
349 yaw = 270;
350 if (yaw < 0)
351 yaw += 360;
352
353 forward = sqrt (dist[0]*dist[0] + dist[1]*dist[1]);
354 pitch = (atan2 (dist[2], forward) * -(180.0f / M_PI));
355 if (pitch < 0)
356 pitch += 360.0;
357 }
358
359 // add new entities for the beams
360 d = VectorNormalizef (dist, dist);
361
362 memset (&ent, 0, sizeof (ent));
363 if (b->model == cgMedia.lightningModel) {
364 model_length = 35.0;
365 d-= 20.0; // correction so it doesn't end in middle of tesla
366 }
367 else {
368 model_length = 30.0;
369 }
370 steps = ceil(d/model_length);
371 len = (d-model_length)/(steps-1);
372
373 // PMM - special case for lightning model .. if the real length is shorter than the model,
374 // flip it around & draw it from the end to the start. This prevents the model from going
375 // through the tesla mine (instead it goes through the target)
376 if (b->model == cgMedia.lightningModel && d <= model_length) {
377 Vec3Copy (b->end, ent.origin);
378 // offset to push beam outside of tesla model (negative because dist is from end to start
379
380 ent.model = b->model;
381 ent.flags = RF_FULLBRIGHT;
382 ent.scale = 1;
383 Vec3Set (angles, pitch, yaw, frand () * 360);
384
385 if (angles[0] || angles[1] || angles[2])
386 Angles_Matrix3 (angles, ent.axis);
387 else
388 Matrix3_Identity (ent.axis);
389 Vec4Set (ent.color, 255, 255, 255, 255);
390 cgi.R_AddEntity (&ent);
391 return;
392 }
393
394 while (d > 0) {
395 Vec3Copy (org, ent.origin);
396 ent.model = b->model;
397 if (b->model == cgMedia.lightningModel) {
398 ent.flags = RF_FULLBRIGHT;
399 Vec3Set (angles, -pitch, yaw + 180.0, frand () * 360);
400 }
401 else
402 Vec3Set (angles, pitch, yaw, frand () * 360);
403
404 ent.scale = 1;
405 if (angles[0] || angles[1] || angles[2])
406 Angles_Matrix3 (angles, ent.axis);
407 else
408 Matrix3_Identity (ent.axis);
409 Vec4Set (ent.color, 255, 255, 255, 255);
410 cgi.R_AddEntity (&ent);
411
412 for (j=0 ; j<3 ; j++)
413 org[j] += dist[j]*len;
414 d -= model_length;
415 }
416 }
417 }
418
419
420 /*
421 =================
422 CG_AddPlayerBeams
423 =================
424 */
CG_AddPlayerBeams(void)425 static void CG_AddPlayerBeams (void)
426 {
427 teBeam_t *b;
428 vec3_t dist, org, angles;
429 int i, j, framenum;
430 float d, yaw, pitch, forward, len, steps;
431 refEntity_t ent;
432 float model_length, hand_multiplier;
433 frame_t *oldframe;
434 playerStateNew_t *ps, *ops;
435
436 //PMM
437 if (hand) {
438 if (hand->intVal == 2)
439 hand_multiplier = 0;
440 else if (hand->intVal == 1)
441 hand_multiplier = -1;
442 else
443 hand_multiplier = 1;
444 }
445 else
446 hand_multiplier = 1;
447 //PMM
448
449 // update beams
450 for (i=0, b=cgPlayerBeams ; i<MAX_TENT_BEAMS ; i++, b++) {
451 vec3_t fwd, right, up;
452 if (!b->model || (b->endtime < cg.realTime))
453 continue;
454
455 if (cgMedia.heatBeamModel && b->model == cgMedia.heatBeamModel) {
456 // if coming from the player, update the start position
457 // entity 0 is the world
458 if (b->entity == cg.playerNum+1) {
459 // set up gun position
460 // code straight out of CG_AddViewWeapon
461 ps = &cg.frame.playerState;
462 oldframe = &cg.oldFrame;
463 if (oldframe->serverFrame != cg.frame.serverFrame-1 || !oldframe->valid)
464 oldframe = &cg.frame; // previous frame was dropped or involid
465 ops = &oldframe->playerState;
466 for (j=0 ; j<3 ; j++) {
467 b->start[j] = cg.refDef.viewOrigin[j] + ops->gunOffset[j]
468 + cg.lerpFrac * (ps->gunOffset[j] - ops->gunOffset[j]);
469 }
470 Vec3MA (b->start, (hand_multiplier * b->offset[0]), cg.refDef.rightVec, org);
471 Vec3MA (org, b->offset[1], cg.refDef.viewAxis[0], org);
472 Vec3MA (org, b->offset[2], cg.refDef.viewAxis[2], org);
473
474 if (hand->intVal == 2)
475 Vec3MA (org, -1, cg.refDef.viewAxis[2], org);
476
477 // FIXME - take these out when final
478 Vec3Copy (cg.refDef.rightVec, right);
479 Vec3Copy (cg.refDef.viewAxis[0], fwd);
480 Vec3Copy (cg.refDef.viewAxis[2], up);
481
482 }
483 else
484 Vec3Copy (b->start, org);
485 }
486 else {
487 // if coming from the player, update the start position
488 // entity 0 is the world
489 if (b->entity == cg.playerNum+1) {
490 Vec3Copy (cg.refDef.viewOrigin, b->start);
491 b->start[2] -= 22; // adjust for view height
492 }
493 Vec3Add (b->start, b->offset, org);
494 }
495
496 // calculate pitch and yaw
497 Vec3Subtract (b->end, org, dist);
498
499 //PMM
500 if (cgMedia.heatBeamModel && b->model == cgMedia.heatBeamModel && b->entity == cg.playerNum+1) {
501 float len;
502
503 len = Vec3Length (dist);
504 Vec3Scale (fwd, len, dist);
505 Vec3MA (dist, (hand_multiplier * b->offset[0]), right, dist);
506 Vec3MA (dist, b->offset[1], fwd, dist);
507 Vec3MA (dist, b->offset[2], up, dist);
508
509 if (hand && (hand->intVal == 2))
510 Vec3MA (org, -1, cg.refDef.viewAxis[2], org);
511 }
512 //PMM
513
514 if ((dist[1] == 0) && (dist[0] == 0)) {
515 yaw = 0;
516 if (dist[2] > 0)
517 pitch = 90;
518 else
519 pitch = 270;
520 }
521 else {
522 // PMM - fixed to correct for pitch of 0
523 if (dist[0])
524 yaw = (atan2 (dist[1], dist[0]) * (180.0f / M_PI));
525 else if (dist[1] > 0)
526 yaw = 90;
527 else
528 yaw = 270;
529 if (yaw < 0)
530 yaw += 360;
531
532 forward = sqrt (dist[0]*dist[0] + dist[1]*dist[1]);
533 pitch = (atan2 (dist[2], forward) * -(180.0f / M_PI));
534 if (pitch < 0)
535 pitch += 360.0;
536 }
537
538 framenum = 1;
539 if (cgMedia.heatBeamModel && b->model == cgMedia.heatBeamModel) {
540 if (b->entity != cg.playerNum+1) {
541 framenum = 2;
542
543 Vec3Set (angles, -pitch, yaw + 180.0, frand () * 360);
544 Angles_Vectors (angles, fwd, right, up);
545
546 // if it's a non-origin offset, it's a player, so use the hardcoded player offset
547 if (!Vec3Compare (b->offset, vec3Origin)) {
548 Vec3MA (org, -(b->offset[0])+1, right, org);
549 Vec3MA (org, -(b->offset[1]), fwd, org);
550 Vec3MA (org, -(b->offset[2])-10, up, org);
551 }
552 else {
553 // if it's a monster, do the particle effect
554 CG_MonsterPlasma_Shell (b->start);
555 }
556 }
557 else {
558 framenum = 1;
559 }
560 }
561
562 // if it's the heatBeamModel, draw the particle effect
563 if (cgMedia.heatBeamModel && b->model == cgMedia.heatBeamModel && b->entity == cg.playerNum+1)
564 CG_Heatbeam (org, dist);
565
566 // add new entities for the beams
567 d = VectorNormalizef (dist, dist);
568
569 memset (&ent, 0, sizeof (ent));
570 if (b->model == cgMedia.heatBeamModel)
571 model_length = 32.0;
572 else if (b->model == cgMedia.lightningModel) {
573 model_length = 35.0;
574 d-= 20.0; // correction so it doesn't end in middle of tesla
575 }
576 else
577 model_length = 30.0;
578
579 steps = ceil(d/model_length);
580 len = (d-model_length)/(steps-1);
581
582 // PMM - special case for lightning model .. if the real length is shorter than the model,
583 // flip it around & draw it from the end to the start. This prevents the model from going
584 // through the tesla mine (instead it goes through the target)
585 if (b->model == cgMedia.lightningModel && d <= model_length) {
586 Vec3Copy (b->end, ent.origin);
587 // offset to push beam outside of tesla model (negative because dist is from end to start
588 // for this beam)
589 ent.model = b->model;
590 ent.flags = RF_FULLBRIGHT;
591 ent.scale = 1;
592 Vec3Set (angles, pitch, yaw, frand () * 360);
593 if (angles[0] || angles[1] || angles[2])
594 Angles_Matrix3 (angles, ent.axis);
595 else
596 Matrix3_Identity (ent.axis);
597 Vec4Set (ent.color, 255, 255, 255, 255);
598 cgi.R_AddEntity (&ent);
599 return;
600 }
601
602 while (d > 0) {
603 Vec3Copy (org, ent.origin);
604 ent.model = b->model;
605 if (cgMedia.heatBeamModel && b->model == cgMedia.heatBeamModel) {
606 ent.flags = RF_FULLBRIGHT;
607 Vec3Set (angles, -pitch, yaw + 180.0, frand () * 360);
608 ent.frame = framenum;
609 }
610 else if (b->model == cgMedia.lightningModel) {
611 ent.flags = RF_FULLBRIGHT;
612 Vec3Set (angles, -pitch, yaw + 180.0, frand () * 360);
613 }
614 else
615 Vec3Set (angles, pitch, yaw, frand () * 360);
616
617 ent.scale = 1;
618 if (angles[0] || angles[1] || angles[2])
619 Angles_Matrix3 (angles, ent.axis);
620 else
621 Matrix3_Identity (ent.axis);
622 Vec4Set (ent.color, 255, 255, 255, 255);
623 cgi.R_AddEntity (&ent);
624
625 for (j=0 ; j<3 ; j++)
626 org[j] += dist[j]*len;
627 d -= model_length;
628 }
629 }
630 }
631
632
633 /*
634 =================
635 CG_ParseLightning
636 =================
637 */
CG_ParseLightning(struct refModel_s * model)638 static int CG_ParseLightning (struct refModel_s *model)
639 {
640 int srcEnt, destEnt;
641 vec3_t start, end;
642 teBeam_t *b;
643 int i;
644
645 srcEnt = cgi.MSG_ReadShort ();
646 destEnt = cgi.MSG_ReadShort ();
647
648 cgi.MSG_ReadPos (start);
649 cgi.MSG_ReadPos (end);
650
651 // override any beam with the same source AND destination entities
652 for (i=0, b=cgBeams ; i<MAX_TENT_BEAMS ; i++, b++) {
653 if (b->entity == srcEnt && b->dest_entity == destEnt) {
654 b->entity = srcEnt;
655 b->dest_entity = destEnt;
656 b->model = model;
657 b->endtime = cg.realTime + 200;
658 Vec3Copy (start, b->start);
659 Vec3Copy (end, b->end);
660 Vec3Clear (b->offset);
661 return srcEnt;
662 }
663 }
664
665 // find a free beam
666 for (i=0, b=cgBeams ; i<MAX_TENT_BEAMS ; i++, b++) {
667 if (!b->model || (b->endtime < cg.realTime)) {
668 b->entity = srcEnt;
669 b->dest_entity = destEnt;
670 b->model = model;
671 b->endtime = cg.realTime + 200;
672 Vec3Copy (start, b->start);
673 Vec3Copy (end, b->end);
674 Vec3Clear (b->offset);
675 return srcEnt;
676 }
677 }
678
679 Com_Printf (PRNT_WARNING, "beam list overflow!\n");
680 return srcEnt;
681 }
682
683 /*
684 =============================================================================
685
686 LASER MANAGEMENT
687
688 =============================================================================
689 */
690
691 /*
692 =================
693 CG_AddLasers
694 =================
695 */
CG_AddLasers(void)696 static void CG_AddLasers (void)
697 {
698 teLaser_t *l;
699 int i, j, clr;
700 vec3_t length;
701
702 for (i=0, l=cgLasers ; i<MAX_TENT_LASERS ; i++, l++) {
703 if (l->endtime >= cg.realTime) {
704 Vec3Subtract(l->ent.oldOrigin, l->ent.origin, length);
705
706 clr = ((l->ent.skinNum >> ((rand () & 3)*8)) & 0xff);
707
708 for (j=0 ; j<3 ; j++)
709 CG_BeamTrail (l->ent.origin, l->ent.oldOrigin,
710 clr, l->ent.frame, 0.33 + ((rand () & 1) * 0.1), -2);
711
712 // outer
713 CG_SpawnParticle (
714 l->ent.origin[0], l->ent.origin[1], l->ent.origin[2],
715 length[0], length[1], length[2],
716 0, 0, 0,
717 0, 0, 0,
718 palRed (clr), palGreen (clr), palBlue (clr),
719 palRed (clr), palGreen (clr), palBlue (clr),
720 0.30f, PART_INSTANT,
721 l->ent.frame + ((l->ent.frame * 0.1f) * (rand () & 1)),
722 l->ent.frame + ((l->ent.frame * 0.1f) * (rand () & 1)),
723 PT_BEAM, 0,
724 0, qFalse,
725 PART_STYLE_BEAM,
726 0);
727 }
728 }
729 }
730
731
732 /*
733 =================
734 CG_ParseLaser
735 =================
736 */
CG_ParseLaser(int colors)737 static void CG_ParseLaser (int colors)
738 {
739 vec3_t start, end;
740 teLaser_t *l;
741 int i, j, clr;
742 vec3_t length;
743
744 cgi.MSG_ReadPos (start);
745 cgi.MSG_ReadPos (end);
746
747 for (i=0, l=cgLasers ; i<MAX_TENT_LASERS ; i++, l++) {
748 if (l->endtime < cg.realTime) {
749 Vec3Subtract(end, start, length);
750
751 clr = ((colors >> ((rand () & 3)*8)) & 0xff);
752
753 for (j=0 ; j<3 ; j++)
754 CG_BeamTrail (start, end, clr, 2 + (rand () & 1), 0.30f, -2);
755
756 // outer
757 CG_SpawnParticle (
758 start[0], start[1], start[2],
759 length[0], length[1], length[2],
760 0, 0, 0,
761 0, 0, 0,
762 palRed (clr), palGreen (clr), palBlue (clr),
763 palRed (clr), palGreen (clr), palBlue (clr),
764 0.30f, -2.1f,
765 4 + (0.4f * (rand () & 1)), 4 + (0.4f * (rand () & 1)),
766 PT_BEAM, 0,
767 0, qFalse,
768 PART_STYLE_BEAM,
769 0);
770 return;
771 }
772 }
773 }
774
775 /*
776 =============================================================================
777
778 TENT MANAGEMENT
779
780 =============================================================================
781 */
782
783 /*
784 =================
785 CG_AddTempEnts
786 =================
787 */
CG_AddTempEnts(void)788 void CG_AddTempEnts (void)
789 {
790 CG_AddBeams ();
791 CG_AddPlayerBeams (); // PMM - draw plasma beams
792 CG_AddLasers ();
793
794 CG_AddExploRattles ();
795 }
796
797
798 /*
799 =================
800 CG_ClearTempEnts
801 =================
802 */
CG_ClearTempEnts(void)803 void CG_ClearTempEnts (void)
804 {
805 memset (cgBeams, 0, sizeof (cgBeams));
806 memset (cgLasers, 0, sizeof (cgLasers));
807 memset (cgPlayerBeams, 0, sizeof (cgPlayerBeams));
808
809 CG_ClearExploRattles ();
810 }
811
812
813 /*
814 =================
815 CG_ParseTempEnt
816 =================
817 */
CG_ParseTempEnt(void)818 void CG_ParseTempEnt (void)
819 {
820 int type, cnt, color, r, ent, magnitude;
821 vec3_t pos, pos2, dir;
822
823 type = cgi.MSG_ReadByte ();
824
825 switch (type) {
826 case TE_BLOOD: // bullet hitting flesh
827 cgi.MSG_ReadPos (pos);
828 cgi.MSG_ReadDir (dir);
829 CG_BleedEffect (pos, dir, 10);
830 break;
831
832 case TE_GUNSHOT: // bullet hitting wall
833 case TE_SPARKS:
834 case TE_BULLET_SPARKS:
835 cgi.MSG_ReadPos (pos);
836 cgi.MSG_ReadDir (dir);
837
838 if (type == TE_GUNSHOT)
839 CG_RicochetEffect (pos, dir, 20);
840 else
841 CG_ParticleEffect (pos, dir, 0xe0, 9);
842
843 if (type != TE_SPARKS) {
844 // impact sound
845 cnt = rand()&15;
846 switch (cnt) {
847 case 1: cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.ricochet[0], 1, ATTN_NORM, 0); break;
848 case 2: cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.ricochet[1], 1, ATTN_NORM, 0); break;
849 case 3: cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.ricochet[2], 1, ATTN_NORM, 0); break;
850 default:
851 break;
852 }
853 }
854
855 break;
856
857 case TE_SCREEN_SPARKS:
858 case TE_SHIELD_SPARKS:
859 cgi.MSG_ReadPos (pos);
860 cgi.MSG_ReadDir (dir);
861 if (type == TE_SCREEN_SPARKS)
862 CG_ParticleEffect (pos, dir, 0xd0, 40);
863 else
864 CG_ParticleEffect (pos, dir, 0xb0, 40);
865
866 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.laserHit, 1, ATTN_NORM, 0);
867 break;
868
869 case TE_SHOTGUN: // bullet hitting wall
870 cgi.MSG_ReadPos (pos);
871 cgi.MSG_ReadDir (dir);
872 CG_RicochetEffect (pos, dir, 20);
873 break;
874
875 case TE_SPLASH: // bullet hitting water
876 cnt = cgi.MSG_ReadByte ();
877 cgi.MSG_ReadPos (pos);
878 cgi.MSG_ReadDir (dir);
879 r = cgi.MSG_ReadByte ();
880 if (r > 6)
881 color = 0;
882 else
883 color = r;
884
885 CG_SplashEffect (pos, dir, color, cnt);
886
887 if (r == SPLASH_SPARKS) {
888 r = (rand()%3);
889 switch (r) {
890 case 0: cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.spark[4], 1, ATTN_STATIC, 0); break;
891 case 1: cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.spark[5], 1, ATTN_STATIC, 0); break;
892 default: cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.spark[6], 1, ATTN_STATIC, 0); break;
893 }
894 }
895 break;
896
897 case TE_LASER_SPARKS:
898 cnt = cgi.MSG_ReadByte ();
899 cgi.MSG_ReadPos (pos);
900 cgi.MSG_ReadDir (dir);
901 color = cgi.MSG_ReadByte ();
902 CG_ParticleEffect2 (pos, dir, color, cnt);
903 break;
904
905 case TE_BLUEHYPERBLASTER:
906 cgi.MSG_ReadPos (pos);
907 cgi.MSG_ReadPos (dir);
908 CG_BlasterBlueParticles (pos, dir);
909 break;
910
911 case TE_BLASTER: // blaster hitting wall
912 cgi.MSG_ReadPos (pos);
913 cgi.MSG_ReadDir (dir);
914 CG_BlasterGoldParticles (pos, dir);
915 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.laserHit, 1, ATTN_NORM, 0);
916 break;
917
918 case TE_RAILTRAIL: // railgun effect
919 cgi.MSG_ReadPos (pos);
920 cgi.MSG_ReadPos (pos2);
921
922 CG_RailTrail (pos, pos2);
923 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.mz.railgunFireSfx, 1, ATTN_NORM, 0);
924 break;
925
926 case TE_EXPLOSION2:
927 case TE_GRENADE_EXPLOSION:
928 case TE_GRENADE_EXPLOSION_WATER:
929 cgi.MSG_ReadPos (pos);
930
931 if (type == TE_GRENADE_EXPLOSION_WATER) {
932 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.waterExplo, 1, ATTN_NORM, 0);
933 CG_ExplosionParticles (pos, 1, qFalse, qTrue);
934 }
935 else {
936 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.grenadeExplo, 1, ATTN_NORM, 0);
937 CG_ExplosionParticles (pos, 1, qFalse, qFalse);
938 }
939 break;
940
941 case TE_PLASMA_EXPLOSION:
942 cgi.MSG_ReadPos (pos);
943 CG_ExplosionParticles (pos, 1, qFalse, qFalse);
944 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.rocketExplo, 1, ATTN_NORM, 0);
945 break;
946
947 case TE_EXPLOSION1:
948 case TE_EXPLOSION1_BIG:
949 case TE_ROCKET_EXPLOSION:
950 case TE_ROCKET_EXPLOSION_WATER:
951 case TE_EXPLOSION1_NP:
952 cgi.MSG_ReadPos (pos);
953
954 if (type != TE_EXPLOSION1_BIG && type != TE_EXPLOSION1_NP)
955 CG_ExplosionParticles (pos, 1, qFalse, (type == TE_ROCKET_EXPLOSION_WATER));
956
957 if (type == TE_ROCKET_EXPLOSION_WATER)
958 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.waterExplo, 1, ATTN_NORM, 0);
959 else
960 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.rocketExplo, 1, ATTN_NORM, 0);
961 break;
962
963 case TE_BFG_EXPLOSION:
964 cgi.MSG_ReadPos (pos);
965 CG_ExplosionBFGEffect (pos);
966 break;
967
968 case TE_BFG_BIGEXPLOSION:
969 cgi.MSG_ReadPos (pos);
970 CG_ExplosionBFGParticles (pos);
971 break;
972
973 case TE_BFG_LASER:
974 CG_ParseLaser (0xd0d1d2d3);
975 break;
976
977 case TE_BUBBLETRAIL:
978 cgi.MSG_ReadPos (pos);
979 cgi.MSG_ReadPos (pos2);
980 CG_BubbleTrail (pos, pos2);
981 break;
982
983 case TE_PARASITE_ATTACK:
984 case TE_MEDIC_CABLE_ATTACK:
985 ent = CG_ParseBeam (cgMedia.parasiteSegmentModel);
986 break;
987
988 case TE_BOSSTPORT: // boss teleporting to station
989 cgi.MSG_ReadPos (pos);
990 CG_BigTeleportParticles (pos);
991 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.bigTeleport, 1, ATTN_NONE, 0);
992 break;
993
994 case TE_GRAPPLE_CABLE:
995 ent = CG_ParseBeam2 (cgMedia.grappleCableModel);
996 break;
997
998 case TE_WELDING_SPARKS:
999 cnt = cgi.MSG_ReadByte ();
1000 cgi.MSG_ReadPos (pos);
1001 cgi.MSG_ReadDir (dir);
1002 color = cgi.MSG_ReadByte ();
1003 CG_ParticleEffect2 (pos, dir, color, cnt);
1004
1005 CG_WeldingSparkFlash (pos);
1006 break;
1007
1008 case TE_GREENBLOOD:
1009 cgi.MSG_ReadPos (pos);
1010 cgi.MSG_ReadDir (dir);
1011 CG_BleedGreenEffect (pos, dir, 10);
1012 break;
1013
1014 case TE_TUNNEL_SPARKS:
1015 cnt = cgi.MSG_ReadByte ();
1016 cgi.MSG_ReadPos (pos);
1017 cgi.MSG_ReadDir (dir);
1018 color = cgi.MSG_ReadByte ();
1019 CG_ParticleEffect3 (pos, dir, color, cnt);
1020 break;
1021
1022 case TE_BLASTER2: // green blaster hitting wall
1023 case TE_FLECHETTE: // flechette
1024 cgi.MSG_ReadPos (pos);
1025 cgi.MSG_ReadDir (dir);
1026
1027 if (type == TE_BLASTER2)
1028 CG_BlasterGreenParticles (pos, dir);
1029 else
1030 CG_BlasterGreyParticles (pos, dir);
1031
1032 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.laserHit, 1, ATTN_NORM, 0);
1033 break;
1034
1035 case TE_LIGHTNING:
1036 ent = CG_ParseLightning (cgMedia.lightningModel);
1037 cgi.Snd_StartSound (NULL, ent, CHAN_WEAPON, cgMedia.sfx.lightning, 1, ATTN_NORM, 0);
1038 break;
1039
1040 case TE_DEBUGTRAIL:
1041 cgi.MSG_ReadPos (pos);
1042 cgi.MSG_ReadPos (pos2);
1043 CG_DebugTrail (pos, pos2);
1044 break;
1045
1046 case TE_PLAIN_EXPLOSION:
1047 cgi.MSG_ReadPos (pos);
1048
1049 if (type == TE_ROCKET_EXPLOSION_WATER) {
1050 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.waterExplo, 1, ATTN_NORM, 0);
1051 CG_ExplosionParticles (pos, 1, qFalse, qTrue);
1052 }
1053 else {
1054 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.rocketExplo, 1, ATTN_NORM, 0);
1055 CG_ExplosionParticles (pos, 1, qFalse, qFalse);
1056 }
1057 break;
1058
1059 case TE_FLASHLIGHT:
1060 cgi.MSG_ReadPos (pos);
1061 ent = cgi.MSG_ReadShort ();
1062 CG_Flashlight (ent, pos);
1063 break;
1064
1065 case TE_FORCEWALL:
1066 cgi.MSG_ReadPos (pos);
1067 cgi.MSG_ReadPos (pos2);
1068 color = cgi.MSG_ReadByte ();
1069 CG_ForceWall (pos, pos2, color);
1070 break;
1071
1072 case TE_HEATBEAM:
1073 ent = CG_ParsePlayerBeam (cgMedia.heatBeamModel);
1074 break;
1075
1076 case TE_MONSTER_HEATBEAM:
1077 ent = CG_ParsePlayerBeam (cgMedia.monsterHeatBeamModel);
1078 break;
1079
1080 case TE_HEATBEAM_SPARKS:
1081 cnt = 50;
1082 cgi.MSG_ReadPos (pos);
1083 cgi.MSG_ReadDir (dir);
1084 r = 8;
1085 magnitude = 60;
1086 color = r & 0xff;
1087 CG_ParticleSteamEffect (pos, dir, color, cnt, magnitude);
1088 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.laserHit, 1, ATTN_NORM, 0);
1089 break;
1090
1091 case TE_HEATBEAM_STEAM:
1092 cgi.MSG_ReadPos (pos);
1093 cgi.MSG_ReadDir (dir);
1094 CG_ParticleSteamEffect (pos, dir, 0xe0, 20, 60);
1095 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.laserHit, 1, ATTN_NORM, 0);
1096 break;
1097
1098 case TE_STEAM:
1099 CG_ParseSteam ();
1100 break;
1101
1102 case TE_BUBBLETRAIL2:
1103 cgi.MSG_ReadPos (pos);
1104 cgi.MSG_ReadPos (pos2);
1105
1106 CG_BubbleTrail2 (pos, pos2, 8);
1107
1108 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.laserHit, 1, ATTN_NORM, 0);
1109 break;
1110
1111 case TE_MOREBLOOD:
1112 cgi.MSG_ReadPos (pos);
1113 cgi.MSG_ReadDir (dir);
1114 CG_BleedEffect (pos, dir, 50);
1115 break;
1116
1117 case TE_CHAINFIST_SMOKE:
1118 Vec3Set (dir, 0, 0, 1);
1119
1120 cgi.MSG_ReadPos (pos);
1121
1122 CG_ParticleSmokeEffect (pos, dir, 0, 20, 20);
1123 break;
1124
1125 case TE_ELECTRIC_SPARKS:
1126 cgi.MSG_ReadPos (pos);
1127 cgi.MSG_ReadDir (dir);
1128
1129 CG_ParticleEffect (pos, dir, 0x75, 40);
1130
1131 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.laserHit, 1, ATTN_NORM, 0);
1132 break;
1133
1134 case TE_TRACKER_EXPLOSION:
1135 cgi.MSG_ReadPos (pos);
1136
1137 CG_ColorFlash (pos, 0, 150, -1, -1, -1);
1138 CG_ExplosionColorParticles (pos);
1139
1140 cgi.Snd_StartSound (pos, 0, CHAN_AUTO, cgMedia.sfx.disruptExplo, 1, ATTN_NORM, 0);
1141 break;
1142
1143 case TE_TELEPORT_EFFECT:
1144 case TE_DBALL_GOAL:
1145 cgi.MSG_ReadPos (pos);
1146 CG_TeleportParticles (pos);
1147 break;
1148
1149 case TE_WIDOWBEAMOUT:
1150 CG_ParseWidow ();
1151 break;
1152
1153 case TE_NUKEBLAST:
1154 CG_ParseNuke ();
1155 break;
1156
1157 case TE_WIDOWSPLASH:
1158 cgi.MSG_ReadPos (pos);
1159 CG_WidowSplash (pos);
1160 break;
1161
1162 default:
1163 Com_Error (ERR_DROP, "CG_ParseTempEnt: bad type");
1164 }
1165 }
1166