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