1 /*
2 Copyright (C) 1996-1997 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 #include "quakedef.h"
22 
23 #include "cl_collision.h"
24 #include "image.h"
25 #include "r_shadow.h"
26 
27 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
29 
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
32 {
33 	{PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34 	{PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 	{PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 	{PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 	{PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
38 	{PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 	{PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 	{PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 	{PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 	{PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 	{PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 	{PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 	{PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
46 };
47 
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
50 
51 typedef struct particleeffectinfo_s
52 {
53 	int effectnameindex; // which effect this belongs to
54 	// PARTICLEEFFECT_* bits
55 	int flags;
56 	// blood effects may spawn very few particles, so proper fraction-overflow
57 	// handling is very important, this variable keeps track of the fraction
58 	double particleaccumulator;
59 	// the math is: countabsolute + requestedcount * countmultiplier * quality
60 	// absolute number of particles to spawn, often used for decals
61 	// (unaffected by quality and requestedcount)
62 	float countabsolute;
63 	// multiplier for the number of particles CL_ParticleEffect was told to
64 	// spawn, most effects do not really have a count and hence use 1, so
65 	// this is often the actual count to spawn, not merely a multiplier
66 	float countmultiplier;
67 	// if > 0 this causes the particle to spawn in an evenly spaced line from
68 	// originmins to originmaxs (causing them to describe a trail, not a box)
69 	float trailspacing;
70 	// type of particle to spawn (defines some aspects of behavior)
71 	ptype_t particletype;
72 	// blending mode used on this particle type
73 	pblend_t blendmode;
74 	// orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
75 	porientation_t orientation;
76 	// range of colors to choose from in hex RRGGBB (like HTML color tags),
77 	// randomly interpolated at spawn
78 	unsigned int color[2];
79 	// a random texture is chosen in this range (note the second value is one
80 	// past the last choosable, so for example 8,16 chooses any from 8 up and
81 	// including 15)
82 	// if start and end of the range are the same, no randomization is done
83 	int tex[2];
84 	// range of size values randomly chosen when spawning, plus size increase over time
85 	float size[3];
86 	// range of alpha values randomly chosen when spawning, plus alpha fade
87 	float alpha[3];
88 	// how long the particle should live (note it is also removed if alpha drops to 0)
89 	float time[2];
90 	// how much gravity affects this particle (negative makes it fly up!)
91 	float gravity;
92 	// how much bounce the particle has when it hits a surface
93 	// if negative the particle is removed on impact
94 	float bounce;
95 	// if in air this friction is applied
96 	// if negative the particle accelerates
97 	float airfriction;
98 	// if in liquid (water/slime/lava) this friction is applied
99 	// if negative the particle accelerates
100 	float liquidfriction;
101 	// these offsets are added to the values given to particleeffect(), and
102 	// then an ellipsoid-shaped jitter is added as defined by these
103 	// (they are the 3 radii)
104 	float stretchfactor;
105 	// stretch velocity factor (used for sparks)
106 	float originoffset[3];
107 	float velocityoffset[3];
108 	float originjitter[3];
109 	float velocityjitter[3];
110 	float velocitymultiplier;
111 	// an effect can also spawn a dlight
112 	float lightradiusstart;
113 	float lightradiusfade;
114 	float lighttime;
115 	float lightcolor[3];
116 	qboolean lightshadow;
117 	int lightcubemapnum;
118 	unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
119 	int staintex[2];
120 }
121 particleeffectinfo_t;
122 
123 #define MAX_PARTICLEEFFECTNAME 256
124 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
125 
126 #define MAX_PARTICLEEFFECTINFO 4096
127 
128 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
129 
130 static int particlepalette[256];
131 /*
132 	0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
133 	0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
134 	0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
135 	0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
136 	0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
137 	0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
138 	0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
139 	0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
140 	0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
141 	0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
142 	0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
143 	0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
144 	0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
145 	0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
146 	0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
147 	0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
148 	0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
149 	0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
150 	0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
151 	0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
152 	0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
153 	0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
154 	0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
155 	0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
156 	0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
157 	0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
158 	0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
159 	0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
160 	0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
161 	0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
162 	0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
163 	0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53  // 248-255
164 */
165 
166 int		ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
167 int		ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
168 int		ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
169 
170 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
171 
172 #define MAX_PARTICLETEXTURES 1024
173 // particletexture_t is a rectangle in the particlefonttexture
174 typedef struct particletexture_s
175 {
176 	rtexture_t *texture;
177 	float s1, t1, s2, t2;
178 }
179 particletexture_t;
180 
181 static rtexturepool_t *particletexturepool;
182 static rtexture_t *particlefonttexture;
183 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
184 
185 // texture numbers in particle font
186 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
187 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
188 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
189 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
190 static const int tex_rainsplash = 32;
191 static const int tex_particle = 63;
192 static const int tex_bubble = 62;
193 static const int tex_raindrop = 61;
194 static const int tex_beam = 60;
195 
196 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
197 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
198 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
199 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
200 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
201 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
202 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
203 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
204 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
205 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
206 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
207 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
208 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
209 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
210 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
211 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
212 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
213 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
214 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
215 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
216 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
217 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
218 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
219 
220 
CL_Particles_ParseEffectInfo(const char * textstart,const char * textend)221 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
222 {
223 	int arrayindex;
224 	int argc;
225 	int effectinfoindex;
226 	int linenumber;
227 	particleeffectinfo_t *info = NULL;
228 	const char *text = textstart;
229 	char argv[16][1024];
230 	effectinfoindex = -1;
231 	for (linenumber = 1;;linenumber++)
232 	{
233 		argc = 0;
234 		for (arrayindex = 0;arrayindex < 16;arrayindex++)
235 			argv[arrayindex][0] = 0;
236 		for (;;)
237 		{
238 			if (!COM_ParseToken_Simple(&text, true, false))
239 				return;
240 			if (!strcmp(com_token, "\n"))
241 				break;
242 			if (argc < 16)
243 			{
244 				strlcpy(argv[argc], com_token, sizeof(argv[argc]));
245 				argc++;
246 			}
247 		}
248 		if (argc < 1)
249 			continue;
250 #define checkparms(n) if (argc != (n)) {Con_Printf("effectinfo.txt:%i: error while parsing: %s given %i parameters, should be %i parameters\n", linenumber, argv[0], argc, (n));break;}
251 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
252 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
253 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
254 #define readfloat(var) checkparms(2);var = atof(argv[1])
255 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
256 		if (!strcmp(argv[0], "effect"))
257 		{
258 			int effectnameindex;
259 			checkparms(2);
260 			effectinfoindex++;
261 			if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
262 			{
263 				Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
264 				break;
265 			}
266 			for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
267 			{
268 				if (particleeffectname[effectnameindex][0])
269 				{
270 					if (!strcmp(particleeffectname[effectnameindex], argv[1]))
271 						break;
272 				}
273 				else
274 				{
275 					strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
276 					break;
277 				}
278 			}
279 			// if we run out of names, abort
280 			if (effectnameindex == MAX_PARTICLEEFFECTNAME)
281 			{
282 				Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
283 				break;
284 			}
285 			info = particleeffectinfo + effectinfoindex;
286 			info->effectnameindex = effectnameindex;
287 			info->particletype = pt_alphastatic;
288 			info->blendmode = particletype[info->particletype].blendmode;
289 			info->orientation = particletype[info->particletype].orientation;
290 			info->tex[0] = tex_particle;
291 			info->tex[1] = tex_particle;
292 			info->color[0] = 0xFFFFFF;
293 			info->color[1] = 0xFFFFFF;
294 			info->size[0] = 1;
295 			info->size[1] = 1;
296 			info->alpha[0] = 0;
297 			info->alpha[1] = 256;
298 			info->alpha[2] = 256;
299 			info->time[0] = 9999;
300 			info->time[1] = 9999;
301 			VectorSet(info->lightcolor, 1, 1, 1);
302 			info->lightshadow = true;
303 			info->lighttime = 9999;
304 			info->stretchfactor = 1;
305 			info->staincolor[0] = (unsigned int)-1;
306 			info->staincolor[1] = (unsigned int)-1;
307 			info->staintex[0] = -1;
308 			info->staintex[1] = -1;
309 		}
310 		else if (info == NULL)
311 		{
312 			Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
313 			break;
314 		}
315 		else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
316 		else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
317 		else if (!strcmp(argv[0], "type"))
318 		{
319 			checkparms(2);
320 			if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
321 			else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
322 			else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
323 			else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
324 			else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
325 			else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
326 			else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
327 			else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
328 			else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
329 			else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
330 			else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
331 			else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
332 			else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
333 			info->blendmode = particletype[info->particletype].blendmode;
334 			info->orientation = particletype[info->particletype].orientation;
335 		}
336 		else if (!strcmp(argv[0], "blend"))
337 		{
338 			checkparms(2);
339 			if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
340 			else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
341 			else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
342 			else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
343 		}
344 		else if (!strcmp(argv[0], "orientation"))
345 		{
346 			checkparms(2);
347 			if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
348 			else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
349 			else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
350 			else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
351 			else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
352 		}
353 		else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
354 		else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
355 		else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
356 		else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
357 		else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
358 		else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
359 		else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
360 		else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
361 		else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
362 		else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
363 		else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
364 		else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
365 		else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
366 		else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
367 		else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
368 		else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
369 		else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
370 		else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
371 		else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
372 		else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
373 		else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
374 		else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
375 		else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
376 		else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
377 		else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
378 		else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
379 		else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
380 		else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1;}
381 		else
382 			Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
383 #undef checkparms
384 #undef readints
385 #undef readfloats
386 #undef readint
387 #undef readfloat
388 	}
389 }
390 
CL_ParticleEffectIndexForName(const char * name)391 int CL_ParticleEffectIndexForName(const char *name)
392 {
393 	int i;
394 	for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
395 		if (!strcmp(particleeffectname[i], name))
396 			return i;
397 	return 0;
398 }
399 
CL_ParticleEffectNameForIndex(int i)400 const char *CL_ParticleEffectNameForIndex(int i)
401 {
402 	if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
403 		return NULL;
404 	return particleeffectname[i];
405 }
406 
407 // MUST match effectnameindex_t in client.h
408 static const char *standardeffectnames[EFFECT_TOTAL] =
409 {
410 	"",
411 	"TE_GUNSHOT",
412 	"TE_GUNSHOTQUAD",
413 	"TE_SPIKE",
414 	"TE_SPIKEQUAD",
415 	"TE_SUPERSPIKE",
416 	"TE_SUPERSPIKEQUAD",
417 	"TE_WIZSPIKE",
418 	"TE_KNIGHTSPIKE",
419 	"TE_EXPLOSION",
420 	"TE_EXPLOSIONQUAD",
421 	"TE_TAREXPLOSION",
422 	"TE_TELEPORT",
423 	"TE_LAVASPLASH",
424 	"TE_SMALLFLASH",
425 	"TE_FLAMEJET",
426 	"EF_FLAME",
427 	"TE_BLOOD",
428 	"TE_SPARK",
429 	"TE_PLASMABURN",
430 	"TE_TEI_G3",
431 	"TE_TEI_SMOKE",
432 	"TE_TEI_BIGEXPLOSION",
433 	"TE_TEI_PLASMAHIT",
434 	"EF_STARDUST",
435 	"TR_ROCKET",
436 	"TR_GRENADE",
437 	"TR_BLOOD",
438 	"TR_WIZSPIKE",
439 	"TR_SLIGHTBLOOD",
440 	"TR_KNIGHTSPIKE",
441 	"TR_VORESPIKE",
442 	"TR_NEHAHRASMOKE",
443 	"TR_NEXUIZPLASMA",
444 	"TR_GLOWTRAIL",
445 	"SVC_PARTICLE"
446 };
447 
CL_Particles_LoadEffectInfo(void)448 void CL_Particles_LoadEffectInfo(void)
449 {
450 	int i;
451 	unsigned char *filedata;
452 	fs_offset_t filesize;
453 	memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
454 	memset(particleeffectname, 0, sizeof(particleeffectname));
455 	for (i = 0;i < EFFECT_TOTAL;i++)
456 		strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
457 	filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
458 	if (filedata)
459 	{
460 		CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
461 		Mem_Free(filedata);
462 	}
463 }
464 
465 /*
466 ===============
467 CL_InitParticles
468 ===============
469 */
470 void CL_ReadPointFile_f (void);
CL_Particles_Init(void)471 void CL_Particles_Init (void)
472 {
473 	Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
474 	Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
475 
476 	Cvar_RegisterVariable (&cl_particles);
477 	Cvar_RegisterVariable (&cl_particles_quality);
478 	Cvar_RegisterVariable (&cl_particles_alpha);
479 	Cvar_RegisterVariable (&cl_particles_size);
480 	Cvar_RegisterVariable (&cl_particles_quake);
481 	Cvar_RegisterVariable (&cl_particles_blood);
482 	Cvar_RegisterVariable (&cl_particles_blood_alpha);
483 	Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
484 	Cvar_RegisterVariable (&cl_particles_explosions_sparks);
485 	Cvar_RegisterVariable (&cl_particles_explosions_shell);
486 	Cvar_RegisterVariable (&cl_particles_bulletimpacts);
487 	Cvar_RegisterVariable (&cl_particles_rain);
488 	Cvar_RegisterVariable (&cl_particles_snow);
489 	Cvar_RegisterVariable (&cl_particles_smoke);
490 	Cvar_RegisterVariable (&cl_particles_smoke_alpha);
491 	Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
492 	Cvar_RegisterVariable (&cl_particles_sparks);
493 	Cvar_RegisterVariable (&cl_particles_bubbles);
494 	Cvar_RegisterVariable (&cl_particles_visculling);
495 	Cvar_RegisterVariable (&cl_decals);
496 	Cvar_RegisterVariable (&cl_decals_visculling);
497 	Cvar_RegisterVariable (&cl_decals_time);
498 	Cvar_RegisterVariable (&cl_decals_fadetime);
499 }
500 
CL_Particles_Shutdown(void)501 void CL_Particles_Shutdown (void)
502 {
503 }
504 
505 // list of all 26 parameters:
506 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
507 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
508 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
509 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
510 // palpha - opacity of particle as 0-255 (can be more than 255)
511 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
512 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
513 // pgravity - how much effect gravity has on the particle (0-1)
514 // pbounce - how much bounce the particle has when it hits a surface (0-1), -1 makes a blood splat when it hits a surface, 0 does not even check for collisions
515 // px,py,pz - starting origin of particle
516 // pvx,pvy,pvz - starting velocity of particle
517 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
518 // blendmode - one of the PBLEND_ values
519 // orientation - one of the PARTICLE_ values
520 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
521 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
CL_NewParticle(unsigned short ptypeindex,int pcolor1,int pcolor2,int ptex,float psize,float psizeincrease,float palpha,float palphafade,float pgravity,float pbounce,float px,float py,float pz,float pvx,float pvy,float pvz,float pairfriction,float pliquidfriction,float originjitter,float velocityjitter,qboolean pqualityreduction,float lifetime,float stretch,pblend_t blendmode,porientation_t orientation,int staincolor1,int staincolor2,int staintex)522 static particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex)
523 {
524 	int l1, l2, r, g, b;
525 	particle_t *part;
526 	vec3_t v;
527 	if (!cl_particles.integer)
528 		return NULL;
529 	for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
530 	if (cl.free_particle >= cl.max_particles)
531 		return NULL;
532 	if (!lifetime)
533 		lifetime = palpha / min(1, palphafade);
534 	part = &cl.particles[cl.free_particle++];
535 	if (cl.num_particles < cl.free_particle)
536 		cl.num_particles = cl.free_particle;
537 	memset(part, 0, sizeof(*part));
538 	part->typeindex = ptypeindex;
539 	part->blendmode = blendmode;
540 	if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
541 	{
542 		particletexture_t *tex = &particletexture[ptex];
543 		if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
544 			part->orientation = PARTICLE_VBEAM;
545 		else
546 			part->orientation = PARTICLE_HBEAM;
547 	}
548 	else
549 		part->orientation = orientation;
550 	l2 = (int)lhrandom(0.5, 256.5);
551 	l1 = 256 - l2;
552 	part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
553 	part->color[1] = ((((pcolor1 >>  8) & 0xFF) * l1 + ((pcolor2 >>  8) & 0xFF) * l2) >> 8) & 0xFF;
554 	part->color[2] = ((((pcolor1 >>  0) & 0xFF) * l1 + ((pcolor2 >>  0) & 0xFF) * l2) >> 8) & 0xFF;
555 	part->staintexnum = staintex;
556 	if(staincolor1 >= 0 && staincolor2 >= 0)
557 	{
558 		l2 = (int)lhrandom(0.5, 256.5);
559 		l1 = 256 - l2;
560 		if(blendmode == PBLEND_INVMOD)
561 		{
562 			r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
563 			g = ((((staincolor1 >>  8) & 0xFF) * l1 + ((staincolor2 >>  8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
564 			b = ((((staincolor1 >>  0) & 0xFF) * l1 + ((staincolor2 >>  0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
565 		}
566 		else
567 		{
568 			r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
569 			g = ((((staincolor1 >>  8) & 0xFF) * l1 + ((staincolor2 >>  8) & 0xFF) * l2) * part->color[1]) / 0x8000;
570 			b = ((((staincolor1 >>  0) & 0xFF) * l1 + ((staincolor2 >>  0) & 0xFF) * l2) * part->color[2]) / 0x8000;
571 		}
572 		if(r > 0xFF) r = 0xFF;
573 		if(g > 0xFF) g = 0xFF;
574 		if(b > 0xFF) b = 0xFF;
575 	}
576 	else
577 	{
578 		r = part->color[0]; // -1 is shorthand for stain = particle color
579 		g = part->color[1];
580 		b = part->color[2];
581 	}
582 	part->staincolor = r * 65536 + g * 256 + b;
583 	part->texnum = ptex;
584 	part->size = psize;
585 	part->sizeincrease = psizeincrease;
586 	part->alpha = palpha;
587 	part->alphafade = palphafade;
588 	part->gravity = pgravity;
589 	part->bounce = pbounce;
590 	part->stretch = stretch;
591 	VectorRandom(v);
592 	part->org[0] = px + originjitter * v[0];
593 	part->org[1] = py + originjitter * v[1];
594 	part->org[2] = pz + originjitter * v[2];
595 	part->vel[0] = pvx + velocityjitter * v[0];
596 	part->vel[1] = pvy + velocityjitter * v[1];
597 	part->vel[2] = pvz + velocityjitter * v[2];
598 	part->time2 = 0;
599 	part->airfriction = pairfriction;
600 	part->liquidfriction = pliquidfriction;
601 	part->die = cl.time + lifetime;
602 	part->delayedcollisions = 0;
603 	part->qualityreduction = pqualityreduction;
604 	// if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
605 	if (part->typeindex == pt_rain)
606 	{
607 		int i;
608 		particle_t *part2;
609 		float lifetime = part->die - cl.time;
610 		vec3_t endvec;
611 		trace_t trace;
612 		// turn raindrop into simple spark and create delayedspawn splash effect
613 		part->typeindex = pt_spark;
614 		part->bounce = 0;
615 		VectorMA(part->org, lifetime, part->vel, endvec);
616 		trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
617 		part->die = cl.time + lifetime * trace.fraction;
618 		part2 = CL_NewParticle(pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1);
619 		if (part2)
620 		{
621 			part2->delayedspawn = part->die;
622 			part2->die += part->die - cl.time;
623 			for (i = rand() & 7;i < 10;i++)
624 			{
625 				part2 = CL_NewParticle(pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
626 				if (part2)
627 				{
628 					part2->delayedspawn = part->die;
629 					part2->die += part->die - cl.time;
630 				}
631 			}
632 		}
633 	}
634 	else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
635 	{
636 		float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
637 		vec3_t endvec;
638 		trace_t trace;
639 		VectorMA(part->org, lifetime, part->vel, endvec);
640 		trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
641 		part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
642 	}
643 	return part;
644 }
645 
CL_SpawnDecalParticleForSurface(int hitent,const vec3_t org,const vec3_t normal,int color1,int color2,int texnum,float size,float alpha)646 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
647 {
648 	int l1, l2;
649 	decal_t *decal;
650 	if (!cl_decals.integer)
651 		return;
652 	for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
653 	if (cl.free_decal >= cl.max_decals)
654 		return;
655 	decal = &cl.decals[cl.free_decal++];
656 	if (cl.num_decals < cl.free_decal)
657 		cl.num_decals = cl.free_decal;
658 	memset(decal, 0, sizeof(*decal));
659 	decal->typeindex = pt_decal;
660 	decal->texnum = texnum;
661 	VectorAdd(org, normal, decal->org);
662 	VectorCopy(normal, decal->normal);
663 	decal->size = size;
664 	decal->alpha = alpha;
665 	decal->time2 = cl.time;
666 	l2 = (int)lhrandom(0.5, 256.5);
667 	l1 = 256 - l2;
668 	decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
669 	decal->color[1] = ((((color1 >>  8) & 0xFF) * l1 + ((color2 >>  8) & 0xFF) * l2) >> 8) & 0xFF;
670 	decal->color[2] = ((((color1 >>  0) & 0xFF) * l1 + ((color2 >>  0) & 0xFF) * l2) >> 8) & 0xFF;
671 	decal->owner = hitent;
672 	decal->clusterindex = -1000; // no vis culling unless we're sure
673 	if (hitent)
674 	{
675 		// these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
676 		decal->ownermodel = cl.entities[decal->owner].render.model;
677 		Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
678 		Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
679 	}
680 	else
681 	{
682 		if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
683 		{
684 			mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
685 			if(leaf)
686 				decal->clusterindex = leaf->clusterindex;
687 		}
688 	}
689 }
690 
CL_SpawnDecalParticleForPoint(const vec3_t org,float maxdist,float size,float alpha,int texnum,int color1,int color2)691 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
692 {
693 	int i;
694 	float bestfrac, bestorg[3], bestnormal[3];
695 	float org2[3];
696 	int besthitent = 0, hitent;
697 	trace_t trace;
698 	bestfrac = 10;
699 	for (i = 0;i < 32;i++)
700 	{
701 		VectorRandom(org2);
702 		VectorMA(org, maxdist, org2, org2);
703 		trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
704 		// take the closest trace result that doesn't end up hitting a NOMARKS
705 		// surface (sky for example)
706 		if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
707 		{
708 			bestfrac = trace.fraction;
709 			besthitent = hitent;
710 			VectorCopy(trace.endpos, bestorg);
711 			VectorCopy(trace.plane.normal, bestnormal);
712 		}
713 	}
714 	if (bestfrac < 1)
715 		CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
716 }
717 
718 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
719 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
CL_ParticleEffect_Fallback(int effectnameindex,float count,const vec3_t originmins,const vec3_t originmaxs,const vec3_t velocitymins,const vec3_t velocitymaxs,entity_t * ent,int palettecolor,qboolean spawndlight,qboolean spawnparticles)720 void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
721 {
722 	vec3_t center;
723 	matrix4x4_t tempmatrix;
724 	VectorLerp(originmins, 0.5, originmaxs, center);
725 	Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
726 	if (effectnameindex == EFFECT_SVC_PARTICLE)
727 	{
728 		if (cl_particles.integer)
729 		{
730 			// bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
731 			if (count == 1024)
732 				CL_ParticleExplosion(center);
733 			else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
734 				CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
735 			else
736 			{
737 				count *= cl_particles_quality.value;
738 				for (;count > 0;count--)
739 				{
740 					int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
741 					CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
742 				}
743 			}
744 		}
745 	}
746 	else if (effectnameindex == EFFECT_TE_WIZSPIKE)
747 		CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
748 	else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
749 		CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
750 	else if (effectnameindex == EFFECT_TE_SPIKE)
751 	{
752 		if (cl_particles_bulletimpacts.integer)
753 		{
754 			if (cl_particles_quake.integer)
755 			{
756 				if (cl_particles_smoke.integer)
757 					CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
758 			}
759 			else
760 			{
761 				CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
762 				CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
763 				CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
764 			}
765 		}
766 		// bullet hole
767 		R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
768 		CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
769 	}
770 	else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
771 	{
772 		if (cl_particles_bulletimpacts.integer)
773 		{
774 			if (cl_particles_quake.integer)
775 			{
776 				if (cl_particles_smoke.integer)
777 					CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
778 			}
779 			else
780 			{
781 				CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
782 				CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
783 				CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
784 			}
785 		}
786 		// bullet hole
787 		R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
788 		CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
789 		CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
790 	}
791 	else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
792 	{
793 		if (cl_particles_bulletimpacts.integer)
794 		{
795 			if (cl_particles_quake.integer)
796 			{
797 				if (cl_particles_smoke.integer)
798 					CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
799 			}
800 			else
801 			{
802 				CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
803 				CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
804 				CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
805 			}
806 		}
807 		// bullet hole
808 		R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
809 		CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
810 	}
811 	else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
812 	{
813 		if (cl_particles_bulletimpacts.integer)
814 		{
815 			if (cl_particles_quake.integer)
816 			{
817 				if (cl_particles_smoke.integer)
818 					CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
819 			}
820 			else
821 			{
822 				CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
823 				CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
824 				CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
825 			}
826 		}
827 		// bullet hole
828 		R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
829 		CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
830 		CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
831 	}
832 	else if (effectnameindex == EFFECT_TE_BLOOD)
833 	{
834 		if (!cl_particles_blood.integer)
835 			return;
836 		if (cl_particles_quake.integer)
837 			CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
838 		else
839 		{
840 			static double bloodaccumulator = 0;
841 			//CL_NewParticle(pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
842 			bloodaccumulator += count * 0.333 * cl_particles_quality.value;
843 			for (;bloodaccumulator > 0;bloodaccumulator--)
844 				CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 1, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
845 		}
846 	}
847 	else if (effectnameindex == EFFECT_TE_SPARK)
848 		CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
849 	else if (effectnameindex == EFFECT_TE_PLASMABURN)
850 	{
851 		// plasma scorch mark
852 		R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
853 		CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
854 		CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
855 	}
856 	else if (effectnameindex == EFFECT_TE_GUNSHOT)
857 	{
858 		if (cl_particles_bulletimpacts.integer)
859 		{
860 			if (cl_particles_quake.integer)
861 				CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
862 			else
863 			{
864 				CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
865 				CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
866 				CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
867 			}
868 		}
869 		// bullet hole
870 		R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
871 		CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
872 	}
873 	else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
874 	{
875 		if (cl_particles_bulletimpacts.integer)
876 		{
877 			if (cl_particles_quake.integer)
878 				CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
879 			else
880 			{
881 				CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
882 				CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
883 				CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
884 			}
885 		}
886 		// bullet hole
887 		R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
888 		CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
889 		CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
890 	}
891 	else if (effectnameindex == EFFECT_TE_EXPLOSION)
892 	{
893 		CL_ParticleExplosion(center);
894 		CL_AllocLightFlash(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
895 	}
896 	else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
897 	{
898 		CL_ParticleExplosion(center);
899 		CL_AllocLightFlash(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
900 	}
901 	else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
902 	{
903 		if (cl_particles_quake.integer)
904 		{
905 			int i;
906 			for (i = 0;i < 1024 * cl_particles_quality.value;i++)
907 			{
908 				if (i & 1)
909 					CL_NewParticle(pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
910 				else
911 					CL_NewParticle(pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
912 			}
913 		}
914 		else
915 			CL_ParticleExplosion(center);
916 		CL_AllocLightFlash(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
917 	}
918 	else if (effectnameindex == EFFECT_TE_SMALLFLASH)
919 		CL_AllocLightFlash(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
920 	else if (effectnameindex == EFFECT_TE_FLAMEJET)
921 	{
922 		count *= cl_particles_quality.value;
923 		while (count-- > 0)
924 			CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
925 	}
926 	else if (effectnameindex == EFFECT_TE_LAVASPLASH)
927 	{
928 		float i, j, inc, vel;
929 		vec3_t dir, org;
930 
931 		inc = 8 / cl_particles_quality.value;
932 		for (i = -128;i < 128;i += inc)
933 		{
934 			for (j = -128;j < 128;j += inc)
935 			{
936 				dir[0] = j + lhrandom(0, inc);
937 				dir[1] = i + lhrandom(0, inc);
938 				dir[2] = 256;
939 				org[0] = center[0] + dir[0];
940 				org[1] = center[1] + dir[1];
941 				org[2] = center[2] + lhrandom(0, 64);
942 				vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
943 				CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
944 			}
945 		}
946 	}
947 	else if (effectnameindex == EFFECT_TE_TELEPORT)
948 	{
949 		float i, j, k, inc, vel;
950 		vec3_t dir;
951 
952 		if (cl_particles_quake.integer)
953 			inc = 4 / cl_particles_quality.value;
954 		else
955 			inc = 8 / cl_particles_quality.value;
956 		for (i = -16;i < 16;i += inc)
957 		{
958 			for (j = -16;j < 16;j += inc)
959 			{
960 				for (k = -24;k < 32;k += inc)
961 				{
962 					VectorSet(dir, i*8, j*8, k*8);
963 					VectorNormalize(dir);
964 					vel = lhrandom(50, 113);
965 					if (cl_particles_quake.integer)
966 						CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
967 					else
968 						CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
969 				}
970 			}
971 		}
972 		if (!cl_particles_quake.integer)
973 			CL_NewParticle(pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
974 		CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
975 	}
976 	else if (effectnameindex == EFFECT_TE_TEI_G3)
977 		CL_NewParticle(pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1);
978 	else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
979 	{
980 		if (cl_particles_smoke.integer)
981 		{
982 			count *= 0.25f * cl_particles_quality.value;
983 			while (count-- > 0)
984 				CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
985 		}
986 	}
987 	else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
988 	{
989 		CL_ParticleExplosion(center);
990 		CL_AllocLightFlash(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
991 	}
992 	else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
993 	{
994 		float f;
995 		R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
996 		CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
997 		if (cl_particles_smoke.integer)
998 			for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
999 				CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1000 		if (cl_particles_sparks.integer)
1001 			for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1002 				CL_NewParticle(pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1003 		CL_AllocLightFlash(NULL, &tempmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1004 	}
1005 	else if (effectnameindex == EFFECT_EF_FLAME)
1006 	{
1007 		count *= 300 * cl_particles_quality.value;
1008 		while (count-- > 0)
1009 			CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1010 		CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1011 	}
1012 	else if (effectnameindex == EFFECT_EF_STARDUST)
1013 	{
1014 		count *= 200 * cl_particles_quality.value;
1015 		while (count-- > 0)
1016 			CL_NewParticle(pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1017 		CL_AllocLightFlash(NULL, &tempmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1018 	}
1019 	else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1020 	{
1021 		vec3_t dir, pos;
1022 		float len, dec, qd;
1023 		int smoke, blood, bubbles, r, color;
1024 
1025 		if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1026 		{
1027 			vec4_t light;
1028 			Vector4Set(light, 0, 0, 0, 0);
1029 
1030 			if (effectnameindex == EFFECT_TR_ROCKET)
1031 				Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1032 			else if (effectnameindex == EFFECT_TR_VORESPIKE)
1033 			{
1034 				if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1035 					Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1036 				else
1037 					Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1038 			}
1039 			else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1040 				Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1041 
1042 			if (light[3])
1043 			{
1044 				matrix4x4_t tempmatrix;
1045 				Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1046 				R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1047 				r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1048 			}
1049 		}
1050 
1051 		if (!spawnparticles)
1052 			return;
1053 
1054 		if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1055 			return;
1056 
1057 		VectorSubtract(originmaxs, originmins, dir);
1058 		len = VectorNormalizeLength(dir);
1059 		if (ent)
1060 		{
1061 			dec = -ent->persistent.trail_time;
1062 			ent->persistent.trail_time += len;
1063 			if (ent->persistent.trail_time < 0.01f)
1064 				return;
1065 
1066 			// if we skip out, leave it reset
1067 			ent->persistent.trail_time = 0.0f;
1068 		}
1069 		else
1070 			dec = 0;
1071 
1072 		// advance into this frame to reach the first puff location
1073 		VectorMA(originmins, dec, dir, pos);
1074 		len -= dec;
1075 
1076 		smoke = cl_particles.integer && cl_particles_smoke.integer;
1077 		blood = cl_particles.integer && cl_particles_blood.integer;
1078 		bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1079 		qd = 1.0f / cl_particles_quality.value;
1080 
1081 		while (len >= 0)
1082 		{
1083 			dec = 3;
1084 			if (blood)
1085 			{
1086 				if (effectnameindex == EFFECT_TR_BLOOD)
1087 				{
1088 					if (cl_particles_quake.integer)
1089 					{
1090 						color = particlepalette[67 + (rand()&3)];
1091 						CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1092 					}
1093 					else
1094 					{
1095 						dec = 16;
1096 						CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
1097 					}
1098 				}
1099 				else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1100 				{
1101 					if (cl_particles_quake.integer)
1102 					{
1103 						dec = 6;
1104 						color = particlepalette[67 + (rand()&3)];
1105 						CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1106 					}
1107 					else
1108 					{
1109 						dec = 32;
1110 						CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
1111 					}
1112 				}
1113 			}
1114 			if (smoke)
1115 			{
1116 				if (effectnameindex == EFFECT_TR_ROCKET)
1117 				{
1118 					if (cl_particles_quake.integer)
1119 					{
1120 						r = rand()&3;
1121 						color = particlepalette[ramp3[r]];
1122 						CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1123 					}
1124 					else
1125 					{
1126 						CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1127 						CL_NewParticle(pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1128 					}
1129 				}
1130 				else if (effectnameindex == EFFECT_TR_GRENADE)
1131 				{
1132 					if (cl_particles_quake.integer)
1133 					{
1134 						r = 2 + (rand()%5);
1135 						color = particlepalette[ramp3[r]];
1136 						CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1137 					}
1138 					else
1139 					{
1140 						CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1141 					}
1142 				}
1143 				else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1144 				{
1145 					if (cl_particles_quake.integer)
1146 					{
1147 						dec = 6;
1148 						color = particlepalette[52 + (rand()&7)];
1149 						CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1150 						CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1151 					}
1152 					else if (gamemode == GAME_GOODVSBAD2)
1153 					{
1154 						dec = 6;
1155 						CL_NewParticle(pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1156 					}
1157 					else
1158 					{
1159 						color = particlepalette[20 + (rand()&7)];
1160 						CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1161 					}
1162 				}
1163 				else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1164 				{
1165 					if (cl_particles_quake.integer)
1166 					{
1167 						dec = 6;
1168 						color = particlepalette[230 + (rand()&7)];
1169 						CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1170 						CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1171 					}
1172 					else
1173 					{
1174 						color = particlepalette[226 + (rand()&7)];
1175 						CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1176 					}
1177 				}
1178 				else if (effectnameindex == EFFECT_TR_VORESPIKE)
1179 				{
1180 					if (cl_particles_quake.integer)
1181 					{
1182 						color = particlepalette[152 + (rand()&3)];
1183 						CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1184 					}
1185 					else if (gamemode == GAME_GOODVSBAD2)
1186 					{
1187 						dec = 6;
1188 						CL_NewParticle(pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1189 					}
1190 					else if (gamemode == GAME_PRYDON)
1191 					{
1192 						dec = 6;
1193 						CL_NewParticle(pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1194 					}
1195 					else
1196 						CL_NewParticle(pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1197 				}
1198 				else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1199 				{
1200 					dec = 7;
1201 					CL_NewParticle(pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1202 				}
1203 				else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1204 				{
1205 					dec = 4;
1206 					CL_NewParticle(pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1207 				}
1208 				else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1209 					CL_NewParticle(pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1210 			}
1211 			if (bubbles)
1212 			{
1213 				if (effectnameindex == EFFECT_TR_ROCKET)
1214 					CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1215 				else if (effectnameindex == EFFECT_TR_GRENADE)
1216 					CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1217 			}
1218 			// advance to next time and position
1219 			dec *= qd;
1220 			len -= dec;
1221 			VectorMA (pos, dec, dir, pos);
1222 		}
1223 		if (ent)
1224 			ent->persistent.trail_time = len;
1225 	}
1226 	else if (developer.integer >= 1)
1227 		Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1228 }
1229 
1230 // this is also called on point effects with spawndlight = true and
1231 // spawnparticles = true
1232 // it is called CL_ParticleTrail because most code does not want to supply
1233 // these parameters, only trail handling does
CL_ParticleTrail(int effectnameindex,float pcount,const vec3_t originmins,const vec3_t originmaxs,const vec3_t velocitymins,const vec3_t velocitymaxs,entity_t * ent,int palettecolor,qboolean spawndlight,qboolean spawnparticles)1234 void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
1235 {
1236 	vec3_t center;
1237 	qboolean found = false;
1238 	if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1239 	{
1240 		Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1241 		return; // no such effect
1242 	}
1243 	VectorLerp(originmins, 0.5, originmaxs, center);
1244 	if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1245 	{
1246 		int effectinfoindex;
1247 		int supercontents;
1248 		int tex, staintex;
1249 		particleeffectinfo_t *info;
1250 		vec3_t center;
1251 		vec3_t centervelocity;
1252 		vec3_t traildir;
1253 		vec3_t trailpos;
1254 		vec3_t rvec;
1255 		vec_t traillen;
1256 		vec_t trailstep;
1257 		qboolean underwater;
1258 		// note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1259 		VectorLerp(originmins, 0.5, originmaxs, center);
1260 		VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1261 		supercontents = CL_PointSuperContents(center);
1262 		underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1263 		VectorSubtract(originmaxs, originmins, traildir);
1264 		traillen = VectorLength(traildir);
1265 		VectorNormalize(traildir);
1266 		for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1267 		{
1268 			if (info->effectnameindex == effectnameindex)
1269 			{
1270 				found = true;
1271 				if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1272 					continue;
1273 				if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1274 					continue;
1275 
1276 				// spawn a dlight if requested
1277 				if (info->lightradiusstart > 0 && spawndlight)
1278 				{
1279 					matrix4x4_t tempmatrix;
1280 					if (info->trailspacing > 0)
1281 						Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1282 					else
1283 						Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1284 					if (info->lighttime > 0 && info->lightradiusfade > 0)
1285 					{
1286 						// light flash (explosion, etc)
1287 						// called when effect starts
1288 						CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0], info->lightcolor[1], info->lightcolor[2], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1289 					}
1290 					else
1291 					{
1292 						// glowing entity
1293 						// called by CL_LinkNetworkEntity
1294 						Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1295 						R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1296 						r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1297 					}
1298 				}
1299 
1300 				if (!spawnparticles)
1301 					continue;
1302 
1303 				// spawn particles
1304 				tex = info->tex[0];
1305 				if (info->tex[1] > info->tex[0])
1306 				{
1307 					tex = (int)lhrandom(info->tex[0], info->tex[1]);
1308 					tex = min(tex, info->tex[1] - 1);
1309 				}
1310 				if(info->staintex[0] < 0)
1311 					staintex = info->staintex[0];
1312 				else
1313 				{
1314 					staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1315 					staintex = min(staintex, info->staintex[1] - 1);
1316 				}
1317 				if (info->particletype == pt_decal)
1318 					CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1]), tex, info->color[0], info->color[1]);
1319 				else if (info->orientation == PARTICLE_HBEAM)
1320 					CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1321 				else
1322 				{
1323 					if (!cl_particles.integer)
1324 						continue;
1325 					switch (info->particletype)
1326 					{
1327 					case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1328 					case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1329 					case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1330 					case pt_blood: if (!cl_particles_blood.integer) continue;break;
1331 					case pt_rain: if (!cl_particles_rain.integer) continue;break;
1332 					case pt_snow: if (!cl_particles_snow.integer) continue;break;
1333 					default: break;
1334 					}
1335 					VectorCopy(originmins, trailpos);
1336 					if (info->trailspacing > 0)
1337 					{
1338 						info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1339 						trailstep = info->trailspacing / cl_particles_quality.value;
1340 					}
1341 					else
1342 					{
1343 						info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1344 						trailstep = 0;
1345 					}
1346 					info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1347 					for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1348 					{
1349 						if (info->tex[1] > info->tex[0])
1350 						{
1351 							tex = (int)lhrandom(info->tex[0], info->tex[1]);
1352 							tex = min(tex, info->tex[1] - 1);
1353 						}
1354 						if (!trailstep)
1355 						{
1356 							trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1357 							trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1358 							trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1359 						}
1360 						VectorRandom(rvec);
1361 						CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1362 						if (trailstep)
1363 							VectorMA(trailpos, trailstep, traildir, trailpos);
1364 					}
1365 				}
1366 			}
1367 		}
1368 	}
1369 	if (!found)
1370 		CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1371 }
1372 
CL_ParticleEffect(int effectnameindex,float pcount,const vec3_t originmins,const vec3_t originmaxs,const vec3_t velocitymins,const vec3_t velocitymaxs,entity_t * ent,int palettecolor)1373 void CL_ParticleEffect(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor)
1374 {
1375 	CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1376 }
1377 
1378 /*
1379 ===============
1380 CL_EntityParticles
1381 ===============
1382 */
CL_EntityParticles(const entity_t * ent)1383 void CL_EntityParticles (const entity_t *ent)
1384 {
1385 	int i;
1386 	float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1387 	static vec3_t avelocities[NUMVERTEXNORMALS];
1388 	if (!cl_particles.integer) return;
1389 	if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1390 
1391 	Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1392 
1393 	if (!avelocities[0][0])
1394 		for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1395 			avelocities[0][i] = lhrandom(0, 2.55);
1396 
1397 	for (i = 0;i < NUMVERTEXNORMALS;i++)
1398 	{
1399 		yaw = cl.time * avelocities[i][0];
1400 		pitch = cl.time * avelocities[i][1];
1401 		v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1402 		v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1403 		v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1404 		CL_NewParticle(pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1405 	}
1406 }
1407 
1408 
CL_ReadPointFile_f(void)1409 void CL_ReadPointFile_f (void)
1410 {
1411 	vec3_t org, leakorg;
1412 	int r, c, s;
1413 	char *pointfile = NULL, *pointfilepos, *t, tchar;
1414 	char name[MAX_OSPATH];
1415 
1416 	if (!cl.worldmodel)
1417 		return;
1418 
1419 	FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1420 	strlcat (name, ".pts", sizeof (name));
1421 	pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1422 	if (!pointfile)
1423 	{
1424 		Con_Printf("Could not open %s\n", name);
1425 		return;
1426 	}
1427 
1428 	Con_Printf("Reading %s...\n", name);
1429 	VectorClear(leakorg);
1430 	c = 0;
1431 	s = 0;
1432 	pointfilepos = pointfile;
1433 	while (*pointfilepos)
1434 	{
1435 		while (*pointfilepos == '\n' || *pointfilepos == '\r')
1436 			pointfilepos++;
1437 		if (!*pointfilepos)
1438 			break;
1439 		t = pointfilepos;
1440 		while (*t && *t != '\n' && *t != '\r')
1441 			t++;
1442 		tchar = *t;
1443 		*t = 0;
1444 #if _MSC_VER >= 1400
1445 #define sscanf sscanf_s
1446 #endif
1447 		r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1448 		*t = tchar;
1449 		pointfilepos = t;
1450 		if (r != 3)
1451 			break;
1452 		if (c == 0)
1453 			VectorCopy(org, leakorg);
1454 		c++;
1455 
1456 		if (cl.num_particles < cl.max_particles - 3)
1457 		{
1458 			s++;
1459 			CL_NewParticle(pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1460 		}
1461 	}
1462 	Mem_Free(pointfile);
1463 	VectorCopy(leakorg, org);
1464 	Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1465 
1466 	CL_NewParticle(pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1);
1467 	CL_NewParticle(pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1);
1468 	CL_NewParticle(pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1);
1469 }
1470 
1471 /*
1472 ===============
1473 CL_ParseParticleEffect
1474 
1475 Parse an effect out of the server message
1476 ===============
1477 */
CL_ParseParticleEffect(void)1478 void CL_ParseParticleEffect (void)
1479 {
1480 	vec3_t org, dir;
1481 	int i, count, msgcount, color;
1482 
1483 	MSG_ReadVector(org, cls.protocol);
1484 	for (i=0 ; i<3 ; i++)
1485 		dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1486 	msgcount = MSG_ReadByte ();
1487 	color = MSG_ReadByte ();
1488 
1489 	if (msgcount == 255)
1490 		count = 1024;
1491 	else
1492 		count = msgcount;
1493 
1494 	CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1495 }
1496 
1497 /*
1498 ===============
1499 CL_ParticleExplosion
1500 
1501 ===============
1502 */
CL_ParticleExplosion(const vec3_t org)1503 void CL_ParticleExplosion (const vec3_t org)
1504 {
1505 	int i;
1506 	trace_t trace;
1507 	//vec3_t v;
1508 	//vec3_t v2;
1509 	R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1510 	CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1511 
1512 	if (cl_particles_quake.integer)
1513 	{
1514 		for (i = 0;i < 1024;i++)
1515 		{
1516 			int r, color;
1517 			r = rand()&3;
1518 			if (i & 1)
1519 			{
1520 				color = particlepalette[ramp1[r]];
1521 				CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1522 			}
1523 			else
1524 			{
1525 				color = particlepalette[ramp2[r]];
1526 				CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1527 			}
1528 		}
1529 	}
1530 	else
1531 	{
1532 		i = CL_PointSuperContents(org);
1533 		if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1534 		{
1535 			if (cl_particles.integer && cl_particles_bubbles.integer)
1536 				for (i = 0;i < 128 * cl_particles_quality.value;i++)
1537 					CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1538 		}
1539 		else
1540 		{
1541 			if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1542 			{
1543 				for (i = 0;i < 512 * cl_particles_quality.value;i++)
1544 				{
1545 					int k;
1546 					vec3_t v, v2;
1547 					for (k = 0;k < 16;k++)
1548 					{
1549 						VectorRandom(v2);
1550 						VectorMA(org, 128, v2, v);
1551 						trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1552 						if (trace.fraction >= 0.1)
1553 							break;
1554 					}
1555 					VectorSubtract(trace.endpos, org, v2);
1556 					VectorScale(v2, 2.0f, v2);
1557 					CL_NewParticle(pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1558 				}
1559 			}
1560 		}
1561 	}
1562 
1563 	if (cl_particles_explosions_shell.integer)
1564 		R_NewExplosion(org);
1565 }
1566 
1567 /*
1568 ===============
1569 CL_ParticleExplosion2
1570 
1571 ===============
1572 */
CL_ParticleExplosion2(const vec3_t org,int colorStart,int colorLength)1573 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1574 {
1575 	int i, k;
1576 	if (!cl_particles.integer) return;
1577 
1578 	for (i = 0;i < 512 * cl_particles_quality.value;i++)
1579 	{
1580 		k = particlepalette[colorStart + (i % colorLength)];
1581 		if (cl_particles_quake.integer)
1582 			CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1583 		else
1584 			CL_NewParticle(pt_alphastatic, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1585 	}
1586 }
1587 
CL_Sparks(const vec3_t originmins,const vec3_t originmaxs,const vec3_t velocitymins,const vec3_t velocitymaxs,float sparkcount)1588 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1589 {
1590 	if (cl_particles_sparks.integer)
1591 	{
1592 		sparkcount *= cl_particles_quality.value;
1593 		while(sparkcount-- > 0)
1594 			CL_NewParticle(pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1595 	}
1596 }
1597 
CL_Smoke(const vec3_t originmins,const vec3_t originmaxs,const vec3_t velocitymins,const vec3_t velocitymaxs,float smokecount)1598 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1599 {
1600 	if (cl_particles_smoke.integer)
1601 	{
1602 		smokecount *= cl_particles_quality.value;
1603 		while(smokecount-- > 0)
1604 			CL_NewParticle(pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1605 	}
1606 }
1607 
CL_ParticleCube(const vec3_t mins,const vec3_t maxs,const vec3_t dir,int count,int colorbase,vec_t gravity,vec_t randomvel)1608 void CL_ParticleCube (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, vec_t gravity, vec_t randomvel)
1609 {
1610 	int k;
1611 	if (!cl_particles.integer) return;
1612 
1613 	count = (int)(count * cl_particles_quality.value);
1614 	while (count--)
1615 	{
1616 		k = particlepalette[colorbase + (rand()&3)];
1617 		CL_NewParticle(pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1618 	}
1619 }
1620 
CL_ParticleRain(const vec3_t mins,const vec3_t maxs,const vec3_t dir,int count,int colorbase,int type)1621 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1622 {
1623 	int k;
1624 	float minz, maxz, lifetime = 30;
1625 	if (!cl_particles.integer) return;
1626 	if (dir[2] < 0) // falling
1627 	{
1628 		minz = maxs[2] + dir[2] * 0.1;
1629 		maxz = maxs[2];
1630 		if (cl.worldmodel)
1631 			lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1632 	}
1633 	else // rising??
1634 	{
1635 		minz = mins[2];
1636 		maxz = maxs[2] + dir[2] * 0.1;
1637 		if (cl.worldmodel)
1638 			lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1639 	}
1640 
1641 	count = (int)(count * cl_particles_quality.value);
1642 
1643 	switch(type)
1644 	{
1645 	case 0:
1646 		if (!cl_particles_rain.integer) break;
1647 		count *= 4; // ick, this should be in the mod or maps?
1648 
1649 		while(count--)
1650 		{
1651 			k = particlepalette[colorbase + (rand()&3)];
1652 			if (gamemode == GAME_GOODVSBAD2)
1653 				CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1654 			else
1655 				CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1656 		}
1657 		break;
1658 	case 1:
1659 		if (!cl_particles_snow.integer) break;
1660 		while(count--)
1661 		{
1662 			k = particlepalette[colorbase + (rand()&3)];
1663 			if (gamemode == GAME_GOODVSBAD2)
1664 				CL_NewParticle(pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1665 			else
1666 				CL_NewParticle(pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1667 		}
1668 		break;
1669 	default:
1670 		Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1671 	}
1672 }
1673 
1674 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1675 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1676 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1677 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1678 
1679 #define PARTICLETEXTURESIZE 64
1680 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1681 
shadebubble(float dx,float dy,vec3_t light)1682 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1683 {
1684 	float dz, f, dot;
1685 	vec3_t normal;
1686 	dz = 1 - (dx*dx+dy*dy);
1687 	if (dz > 0) // it does hit the sphere
1688 	{
1689 		f = 0;
1690 		// back side
1691 		normal[0] = dx;normal[1] = dy;normal[2] = dz;
1692 		VectorNormalize(normal);
1693 		dot = DotProduct(normal, light);
1694 		if (dot > 0.5) // interior reflection
1695 			f += ((dot *  2) - 1);
1696 		else if (dot < -0.5) // exterior reflection
1697 			f += ((dot * -2) - 1);
1698 		// front side
1699 		normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1700 		VectorNormalize(normal);
1701 		dot = DotProduct(normal, light);
1702 		if (dot > 0.5) // interior reflection
1703 			f += ((dot *  2) - 1);
1704 		else if (dot < -0.5) // exterior reflection
1705 			f += ((dot * -2) - 1);
1706 		f *= 128;
1707 		f += 16; // just to give it a haze so you can see the outline
1708 		f = bound(0, f, 255);
1709 		return (unsigned char) f;
1710 	}
1711 	else
1712 		return 0;
1713 }
1714 
1715 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
CL_Particle_PixelCoordsForTexnum(int texnum,int * basex,int * basey,int * width,int * height)1716 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1717 {
1718 	*basex = (texnum % particlefontcols) * particlefontcellwidth;
1719 	*basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1720 	*width = particlefontcellwidth;
1721 	*height = particlefontcellheight;
1722 }
1723 
setuptex(int texnum,unsigned char * data,unsigned char * particletexturedata)1724 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1725 {
1726 	int basex, basey, w, h, y;
1727 	CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1728 	if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1729 		Sys_Error("invalid particle texture size for autogenerating");
1730 	for (y = 0;y < PARTICLETEXTURESIZE;y++)
1731 		memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1732 }
1733 
particletextureblotch(unsigned char * data,float radius,float red,float green,float blue,float alpha)1734 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1735 {
1736 	int x, y;
1737 	float cx, cy, dx, dy, f, iradius;
1738 	unsigned char *d;
1739 	cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1740 	cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1741 	iradius = 1.0f / radius;
1742 	alpha *= (1.0f / 255.0f);
1743 	for (y = 0;y < PARTICLETEXTURESIZE;y++)
1744 	{
1745 		for (x = 0;x < PARTICLETEXTURESIZE;x++)
1746 		{
1747 			dx = (x - cx);
1748 			dy = (y - cy);
1749 			f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1750 			if (f > 0)
1751 			{
1752 				if (f > 1)
1753 					f = 1;
1754 				d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1755 				d[0] += (int)(f * (blue  - d[0]));
1756 				d[1] += (int)(f * (green - d[1]));
1757 				d[2] += (int)(f * (red   - d[2]));
1758 			}
1759 		}
1760 	}
1761 }
1762 
particletextureclamp(unsigned char * data,int minr,int ming,int minb,int maxr,int maxg,int maxb)1763 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1764 {
1765 	int i;
1766 	for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1767 	{
1768 		data[0] = bound(minb, data[0], maxb);
1769 		data[1] = bound(ming, data[1], maxg);
1770 		data[2] = bound(minr, data[2], maxr);
1771 	}
1772 }
1773 
particletextureinvert(unsigned char * data)1774 void particletextureinvert(unsigned char *data)
1775 {
1776 	int i;
1777 	for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1778 	{
1779 		data[0] = 255 - data[0];
1780 		data[1] = 255 - data[1];
1781 		data[2] = 255 - data[2];
1782 	}
1783 }
1784 
1785 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
R_InitBloodTextures(unsigned char * particletexturedata)1786 static void R_InitBloodTextures (unsigned char *particletexturedata)
1787 {
1788 	int i, j, k, m;
1789 	unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1790 
1791 	// blood particles
1792 	for (i = 0;i < 8;i++)
1793 	{
1794 		memset(&data[0][0][0], 255, sizeof(data));
1795 		for (k = 0;k < 24;k++)
1796 			particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1797 		//particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1798 		particletextureinvert(&data[0][0][0]);
1799 		setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1800 	}
1801 
1802 	// blood decals
1803 	for (i = 0;i < 8;i++)
1804 	{
1805 		memset(&data[0][0][0], 255, sizeof(data));
1806 		m = 8;
1807 		for (j = 1;j < 10;j++)
1808 			for (k = min(j, m - 1);k < m;k++)
1809 				particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1810 		//particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1811 		particletextureinvert(&data[0][0][0]);
1812 		setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1813 	}
1814 
1815 }
1816 
1817 //uncomment this to make engine save out particle font to a tga file when run
1818 //#define DUMPPARTICLEFONT
1819 
R_InitParticleTexture(void)1820 static void R_InitParticleTexture (void)
1821 {
1822 	int x, y, d, i, k, m;
1823 	int basex, basey, w, h;
1824 	float dx, dy, f;
1825 	vec3_t light;
1826 	char *buf;
1827 	fs_offset_t filesize;
1828 
1829 	// a note: decals need to modulate (multiply) the background color to
1830 	// properly darken it (stain), and they need to be able to alpha fade,
1831 	// this is a very difficult challenge because it means fading to white
1832 	// (no change to background) rather than black (darkening everything
1833 	// behind the whole decal polygon), and to accomplish this the texture is
1834 	// inverted (dark red blood on white background becomes brilliant cyan
1835 	// and white on black background) so we can alpha fade it to black, then
1836 	// we invert it again during the blendfunc to make it work...
1837 
1838 #ifndef DUMPPARTICLEFONT
1839 	particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1840 	if (particlefonttexture)
1841 	{
1842 		// TODO maybe allow custom grid size?
1843 		particlefontwidth = image_width;
1844 		particlefontheight = image_height;
1845 		particlefontcellwidth = image_width / 8;
1846 		particlefontcellheight = image_height / 8;
1847 		particlefontcols = 8;
1848 		particlefontrows = 8;
1849 	}
1850 	else
1851 #endif
1852 	{
1853 		unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1854 		unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1855 
1856 		particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1857 		particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1858 		particlefontcols = 8;
1859 		particlefontrows = 8;
1860 
1861 		memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1862 
1863 		// smoke
1864 		for (i = 0;i < 8;i++)
1865 		{
1866 			memset(&data[0][0][0], 255, sizeof(data));
1867 			do
1868 			{
1869 				unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1870 
1871 				fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1872 				fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1873 				m = 0;
1874 				for (y = 0;y < PARTICLETEXTURESIZE;y++)
1875 				{
1876 					dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1877 					for (x = 0;x < PARTICLETEXTURESIZE;x++)
1878 					{
1879 						dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1880 						d = (noise2[y][x] - 128) * 3 + 192;
1881 						if (d > 0)
1882 							d = (int)(d * (1-(dx*dx+dy*dy)));
1883 						d = (d * noise1[y][x]) >> 7;
1884 						d = bound(0, d, 255);
1885 						data[y][x][3] = (unsigned char) d;
1886 						if (m < d)
1887 							m = d;
1888 					}
1889 				}
1890 			}
1891 			while (m < 224);
1892 			setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1893 		}
1894 
1895 		// rain splash
1896 		memset(&data[0][0][0], 255, sizeof(data));
1897 		for (y = 0;y < PARTICLETEXTURESIZE;y++)
1898 		{
1899 			dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1900 			for (x = 0;x < PARTICLETEXTURESIZE;x++)
1901 			{
1902 				dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1903 				f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1904 				data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1905 			}
1906 		}
1907 		setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1908 
1909 		// normal particle
1910 		memset(&data[0][0][0], 255, sizeof(data));
1911 		for (y = 0;y < PARTICLETEXTURESIZE;y++)
1912 		{
1913 			dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1914 			for (x = 0;x < PARTICLETEXTURESIZE;x++)
1915 			{
1916 				dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1917 				d = (int)(256 * (1 - (dx*dx+dy*dy)));
1918 				d = bound(0, d, 255);
1919 				data[y][x][3] = (unsigned char) d;
1920 			}
1921 		}
1922 		setuptex(tex_particle, &data[0][0][0], particletexturedata);
1923 
1924 		// rain
1925 		memset(&data[0][0][0], 255, sizeof(data));
1926 		light[0] = 1;light[1] = 1;light[2] = 1;
1927 		VectorNormalize(light);
1928 		for (y = 0;y < PARTICLETEXTURESIZE;y++)
1929 		{
1930 			dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1931 			// stretch upper half of bubble by +50% and shrink lower half by -50%
1932 			// (this gives an elongated teardrop shape)
1933 			if (dy > 0.5f)
1934 				dy = (dy - 0.5f) * 2.0f;
1935 			else
1936 				dy = (dy - 0.5f) / 1.5f;
1937 			for (x = 0;x < PARTICLETEXTURESIZE;x++)
1938 			{
1939 				dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1940 				// shrink bubble width to half
1941 				dx *= 2.0f;
1942 				data[y][x][3] = shadebubble(dx, dy, light);
1943 			}
1944 		}
1945 		setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1946 
1947 		// bubble
1948 		memset(&data[0][0][0], 255, sizeof(data));
1949 		light[0] = 1;light[1] = 1;light[2] = 1;
1950 		VectorNormalize(light);
1951 		for (y = 0;y < PARTICLETEXTURESIZE;y++)
1952 		{
1953 			dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1954 			for (x = 0;x < PARTICLETEXTURESIZE;x++)
1955 			{
1956 				dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1957 				data[y][x][3] = shadebubble(dx, dy, light);
1958 			}
1959 		}
1960 		setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1961 
1962 		// Blood particles and blood decals
1963 		R_InitBloodTextures (particletexturedata);
1964 
1965 		// bullet decals
1966 		for (i = 0;i < 8;i++)
1967 		{
1968 			memset(&data[0][0][0], 255, sizeof(data));
1969 			for (k = 0;k < 12;k++)
1970 				particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1971 			for (k = 0;k < 3;k++)
1972 				particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1973 			//particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1974 			particletextureinvert(&data[0][0][0]);
1975 			setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1976 		}
1977 
1978 #ifdef DUMPPARTICLEFONT
1979 		Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1980 #endif
1981 
1982 		particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1983 
1984 		Mem_Free(particletexturedata);
1985 	}
1986 	for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1987 	{
1988 		CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
1989 		particletexture[i].texture = particlefonttexture;
1990 		particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
1991 		particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
1992 		particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
1993 		particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
1994 	}
1995 
1996 #ifndef DUMPPARTICLEFONT
1997 	particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1998 	if (!particletexture[tex_beam].texture)
1999 #endif
2000 	{
2001 		unsigned char noise3[64][64], data2[64][16][4];
2002 		// nexbeam
2003 		fractalnoise(&noise3[0][0], 64, 4);
2004 		m = 0;
2005 		for (y = 0;y < 64;y++)
2006 		{
2007 			dy = (y - 0.5f*64) / (64*0.5f-1);
2008 			for (x = 0;x < 16;x++)
2009 			{
2010 				dx = (x - 0.5f*16) / (16*0.5f-2);
2011 				d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2012 				data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2013 				data2[y][x][3] = 255;
2014 			}
2015 		}
2016 
2017 #ifdef DUMPPARTICLEFONT
2018 		Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2019 #endif
2020 		particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
2021 	}
2022 	particletexture[tex_beam].s1 = 0;
2023 	particletexture[tex_beam].t1 = 0;
2024 	particletexture[tex_beam].s2 = 1;
2025 	particletexture[tex_beam].t2 = 1;
2026 
2027 	// now load an texcoord/texture override file
2028 	buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2029 	if(buf)
2030 	{
2031 		const char *bufptr;
2032 		bufptr = buf;
2033 		for(;;)
2034 		{
2035 			if(!COM_ParseToken_Simple(&bufptr, true, false))
2036 				break;
2037 			if(!strcmp(com_token, "\n"))
2038 				continue; // empty line
2039 			i = atoi(com_token) % MAX_PARTICLETEXTURES;
2040 			particletexture[i].texture = particlefonttexture;
2041 
2042 			if (!COM_ParseToken_Simple(&bufptr, true, false))
2043 				break;
2044 			if (!strcmp(com_token, "\n"))
2045 			{
2046 				Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2047 				continue;
2048 			}
2049 			particletexture[i].s1 = atof(com_token);
2050 
2051 			if (!COM_ParseToken_Simple(&bufptr, true, false))
2052 				break;
2053 			if (!strcmp(com_token, "\n"))
2054 			{
2055 				Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2056 				continue;
2057 			}
2058 			particletexture[i].t1 = atof(com_token);
2059 
2060 			if (!COM_ParseToken_Simple(&bufptr, true, false))
2061 				break;
2062 			if (!strcmp(com_token, "\n"))
2063 			{
2064 				Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2065 				continue;
2066 			}
2067 			particletexture[i].s2 = atof(com_token);
2068 
2069 			if (!COM_ParseToken_Simple(&bufptr, true, false))
2070 				break;
2071 			if (!strcmp(com_token, "\n"))
2072 			{
2073 				Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2074 				continue;
2075 			}
2076 			particletexture[i].t2 = atof(com_token);
2077 		}
2078 		Mem_Free(buf);
2079 	}
2080 }
2081 
r_part_start(void)2082 static void r_part_start(void)
2083 {
2084 	int i;
2085 	// generate particlepalette for convenience from the main one
2086 	for (i = 0;i < 256;i++)
2087 		particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2088 	particletexturepool = R_AllocTexturePool();
2089 	R_InitParticleTexture ();
2090 	CL_Particles_LoadEffectInfo();
2091 }
2092 
r_part_shutdown(void)2093 static void r_part_shutdown(void)
2094 {
2095 	R_FreeTexturePool(&particletexturepool);
2096 }
2097 
r_part_newmap(void)2098 static void r_part_newmap(void)
2099 {
2100 	CL_Particles_LoadEffectInfo();
2101 }
2102 
2103 #define BATCHSIZE 256
2104 unsigned short particle_elements[BATCHSIZE*6];
2105 
R_Particles_Init(void)2106 void R_Particles_Init (void)
2107 {
2108 	int i;
2109 	for (i = 0;i < BATCHSIZE;i++)
2110 	{
2111 		particle_elements[i*6+0] = i*4+0;
2112 		particle_elements[i*6+1] = i*4+1;
2113 		particle_elements[i*6+2] = i*4+2;
2114 		particle_elements[i*6+3] = i*4+0;
2115 		particle_elements[i*6+4] = i*4+2;
2116 		particle_elements[i*6+5] = i*4+3;
2117 	}
2118 
2119 	Cvar_RegisterVariable(&r_drawparticles);
2120 	Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2121 	Cvar_RegisterVariable(&r_drawdecals);
2122 	Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2123 	R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2124 }
2125 
R_DrawDecal_TransparentCallback(const entity_render_t * ent,const rtlight_t * rtlight,int numsurfaces,int * surfacelist)2126 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2127 {
2128 	int surfacelistindex;
2129 	const decal_t *d;
2130 	float *v3f, *t2f, *c4f;
2131 	particletexture_t *tex;
2132 	float right[3], up[3], size, ca;
2133 	float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
2134 	float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2135 
2136 	r_refdef.stats.decals += numsurfaces;
2137 	R_Mesh_Matrix(&identitymatrix);
2138 	R_Mesh_ResetTextureState();
2139 	R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2140 	R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2141 	R_Mesh_ColorPointer(particle_color4f, 0, 0);
2142 	R_SetupGenericShader(true);
2143 	GL_DepthMask(false);
2144 	GL_DepthRange(0, 1);
2145 	GL_PolygonOffset(0, 0);
2146 	GL_DepthTest(true);
2147 	GL_CullFace(GL_NONE);
2148 
2149 	// generate all the vertices at once
2150 	for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2151 	{
2152 		d = cl.decals + surfacelist[surfacelistindex];
2153 
2154 		// calculate color
2155 		c4f = particle_color4f + 16*surfacelistindex;
2156 		ca = d->alpha * alphascale;
2157 		if (r_refdef.fogenabled)
2158 			ca *= FogPoint_World(d->org);
2159 		Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2160 		Vector4Copy(c4f, c4f + 4);
2161 		Vector4Copy(c4f, c4f + 8);
2162 		Vector4Copy(c4f, c4f + 12);
2163 
2164 		// calculate vertex positions
2165 		size = d->size * cl_particles_size.value;
2166 		VectorVectors(d->normal, right, up);
2167 		VectorScale(right, size, right);
2168 		VectorScale(up, size, up);
2169 		v3f = particle_vertex3f + 12*surfacelistindex;
2170 		v3f[ 0] = d->org[0] - right[0] - up[0];
2171 		v3f[ 1] = d->org[1] - right[1] - up[1];
2172 		v3f[ 2] = d->org[2] - right[2] - up[2];
2173 		v3f[ 3] = d->org[0] - right[0] + up[0];
2174 		v3f[ 4] = d->org[1] - right[1] + up[1];
2175 		v3f[ 5] = d->org[2] - right[2] + up[2];
2176 		v3f[ 6] = d->org[0] + right[0] + up[0];
2177 		v3f[ 7] = d->org[1] + right[1] + up[1];
2178 		v3f[ 8] = d->org[2] + right[2] + up[2];
2179 		v3f[ 9] = d->org[0] + right[0] - up[0];
2180 		v3f[10] = d->org[1] + right[1] - up[1];
2181 		v3f[11] = d->org[2] + right[2] - up[2];
2182 
2183 		// calculate texcoords
2184 		tex = &particletexture[d->texnum];
2185 		t2f = particle_texcoord2f + 8*surfacelistindex;
2186 		t2f[0] = tex->s1;t2f[1] = tex->t2;
2187 		t2f[2] = tex->s1;t2f[3] = tex->t1;
2188 		t2f[4] = tex->s2;t2f[5] = tex->t1;
2189 		t2f[6] = tex->s2;t2f[7] = tex->t2;
2190 	}
2191 
2192 	// now render the decals all at once
2193 	// (this assumes they all use one particle font texture!)
2194 	GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2195 	R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2196 	GL_LockArrays(0, numsurfaces*4);
2197 	R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2198 	GL_LockArrays(0, 0);
2199 }
2200 
R_DrawDecals(void)2201 void R_DrawDecals (void)
2202 {
2203 	int i;
2204 	decal_t *decal;
2205 	float frametime;
2206 	float decalfade;
2207 	float drawdist2;
2208 
2209 	frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2210 	cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2211 
2212 	// LordHavoc: early out conditions
2213 	if ((!cl.num_decals) || (!r_drawdecals.integer))
2214 		return;
2215 
2216 	decalfade = frametime * 256 / cl_decals_fadetime.value;
2217 	drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2218 	drawdist2 = drawdist2*drawdist2;
2219 
2220 	for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2221 	{
2222 		if (!decal->typeindex)
2223 			continue;
2224 
2225 		if (cl.time > decal->time2 + cl_decals_time.value)
2226 		{
2227 			decal->alpha -= decalfade;
2228 			if (decal->alpha <= 0)
2229 				goto killdecal;
2230 		}
2231 
2232 		if (decal->owner)
2233 		{
2234 			if (cl.entities[decal->owner].render.model == decal->ownermodel)
2235 			{
2236 				Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2237 				Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2238 			}
2239 			else
2240 				goto killdecal;
2241 		}
2242 
2243 		if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2244 			continue;
2245 
2246 		if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size))
2247 			R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2248 		continue;
2249 killdecal:
2250 		decal->typeindex = 0;
2251 		if (cl.free_decal > i)
2252 			cl.free_decal = i;
2253 	}
2254 
2255 	// reduce cl.num_decals if possible
2256 	while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2257 		cl.num_decals--;
2258 
2259 	if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2260 	{
2261 		decal_t *olddecals = cl.decals;
2262 		cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2263 		cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2264 		memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2265 		Mem_Free(olddecals);
2266 	}
2267 }
2268 
R_DrawParticle_TransparentCallback(const entity_render_t * ent,const rtlight_t * rtlight,int numsurfaces,int * surfacelist)2269 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2270 {
2271 	int surfacelistindex;
2272 	int batchstart, batchcount;
2273 	const particle_t *p;
2274 	pblend_t blendmode;
2275 	rtexture_t *texture;
2276 	float *v3f, *t2f, *c4f;
2277 	particletexture_t *tex;
2278 	float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2279 	float ambient[3], diffuse[3], diffusenormal[3];
2280 	vec4_t colormultiplier;
2281 	float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2282 
2283 	Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f));
2284 
2285 	r_refdef.stats.particles += numsurfaces;
2286 	R_Mesh_Matrix(&identitymatrix);
2287 	R_Mesh_ResetTextureState();
2288 	R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2289 	R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2290 	R_Mesh_ColorPointer(particle_color4f, 0, 0);
2291 	R_SetupGenericShader(true);
2292 	GL_DepthMask(false);
2293 	GL_DepthRange(0, 1);
2294 	GL_PolygonOffset(0, 0);
2295 	GL_DepthTest(true);
2296 	GL_CullFace(GL_NONE);
2297 
2298 	// first generate all the vertices at once
2299 	for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2300 	{
2301 		p = cl.particles + surfacelist[surfacelistindex];
2302 
2303 		blendmode = p->blendmode;
2304 
2305 		c4f[0] = p->color[0] * colormultiplier[0];
2306 		c4f[1] = p->color[1] * colormultiplier[1];
2307 		c4f[2] = p->color[2] * colormultiplier[2];
2308 		c4f[3] = p->alpha * colormultiplier[3];
2309 		switch (blendmode)
2310 		{
2311 		case PBLEND_INVALID:
2312 		case PBLEND_INVMOD:
2313 		case PBLEND_ADD:
2314 			// additive and modulate can just fade out in fog (this is correct)
2315 			if (r_refdef.fogenabled)
2316 				c4f[3] *= FogPoint_World(p->org);
2317 			// collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2318 			c4f[0] *= c4f[3];
2319 			c4f[1] *= c4f[3];
2320 			c4f[2] *= c4f[3];
2321 			c4f[3] = 1;
2322 			break;
2323 		case PBLEND_ALPHA:
2324 			// note: lighting is not cheap!
2325 			if (particletype[p->typeindex].lighting)
2326 			{
2327 				R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2328 				c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2329 				c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2330 				c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2331 			}
2332 			// mix in the fog color
2333 			if (r_refdef.fogenabled)
2334 			{
2335 				fog = FogPoint_World(p->org);
2336 				ifog = 1 - fog;
2337 				c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2338 				c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2339 				c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2340 			}
2341 			break;
2342 		}
2343 		// copy the color into the other three vertices
2344 		Vector4Copy(c4f, c4f + 4);
2345 		Vector4Copy(c4f, c4f + 8);
2346 		Vector4Copy(c4f, c4f + 12);
2347 
2348 		size = p->size * cl_particles_size.value;
2349 		tex = &particletexture[p->texnum];
2350 		switch(p->orientation)
2351 		{
2352 		case PARTICLE_INVALID:
2353 		case PARTICLE_BILLBOARD:
2354 			VectorScale(r_refdef.view.left, -size * p->stretch, right);
2355 			VectorScale(r_refdef.view.up, size, up);
2356 			v3f[ 0] = p->org[0] - right[0] - up[0];
2357 			v3f[ 1] = p->org[1] - right[1] - up[1];
2358 			v3f[ 2] = p->org[2] - right[2] - up[2];
2359 			v3f[ 3] = p->org[0] - right[0] + up[0];
2360 			v3f[ 4] = p->org[1] - right[1] + up[1];
2361 			v3f[ 5] = p->org[2] - right[2] + up[2];
2362 			v3f[ 6] = p->org[0] + right[0] + up[0];
2363 			v3f[ 7] = p->org[1] + right[1] + up[1];
2364 			v3f[ 8] = p->org[2] + right[2] + up[2];
2365 			v3f[ 9] = p->org[0] + right[0] - up[0];
2366 			v3f[10] = p->org[1] + right[1] - up[1];
2367 			v3f[11] = p->org[2] + right[2] - up[2];
2368 			t2f[0] = tex->s1;t2f[1] = tex->t2;
2369 			t2f[2] = tex->s1;t2f[3] = tex->t1;
2370 			t2f[4] = tex->s2;t2f[5] = tex->t1;
2371 			t2f[6] = tex->s2;t2f[7] = tex->t2;
2372 			break;
2373 		case PARTICLE_ORIENTED_DOUBLESIDED:
2374 			VectorVectors(p->vel, right, up);
2375 			VectorScale(right, size * p->stretch, right);
2376 			VectorScale(up, size, up);
2377 			v3f[ 0] = p->org[0] - right[0] - up[0];
2378 			v3f[ 1] = p->org[1] - right[1] - up[1];
2379 			v3f[ 2] = p->org[2] - right[2] - up[2];
2380 			v3f[ 3] = p->org[0] - right[0] + up[0];
2381 			v3f[ 4] = p->org[1] - right[1] + up[1];
2382 			v3f[ 5] = p->org[2] - right[2] + up[2];
2383 			v3f[ 6] = p->org[0] + right[0] + up[0];
2384 			v3f[ 7] = p->org[1] + right[1] + up[1];
2385 			v3f[ 8] = p->org[2] + right[2] + up[2];
2386 			v3f[ 9] = p->org[0] + right[0] - up[0];
2387 			v3f[10] = p->org[1] + right[1] - up[1];
2388 			v3f[11] = p->org[2] + right[2] - up[2];
2389 			t2f[0] = tex->s1;t2f[1] = tex->t2;
2390 			t2f[2] = tex->s1;t2f[3] = tex->t1;
2391 			t2f[4] = tex->s2;t2f[5] = tex->t1;
2392 			t2f[6] = tex->s2;t2f[7] = tex->t2;
2393 			break;
2394 		case PARTICLE_SPARK:
2395 			len = VectorLength(p->vel);
2396 			VectorNormalize2(p->vel, up);
2397 			lenfactor = p->stretch * 0.04 * len;
2398 			if(lenfactor < size * 0.5)
2399 				lenfactor = size * 0.5;
2400 			VectorMA(p->org, -lenfactor, up, v);
2401 			VectorMA(p->org,  lenfactor, up, up2);
2402 			R_CalcBeam_Vertex3f(v3f, v, up2, size);
2403 			t2f[0] = tex->s1;t2f[1] = tex->t2;
2404 			t2f[2] = tex->s1;t2f[3] = tex->t1;
2405 			t2f[4] = tex->s2;t2f[5] = tex->t1;
2406 			t2f[6] = tex->s2;t2f[7] = tex->t2;
2407 			break;
2408 		case PARTICLE_VBEAM:
2409 			R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2410 			VectorSubtract(p->vel, p->org, up);
2411 			VectorNormalize(up);
2412 			v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2413 			v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2414 			t2f[0] = tex->s2;t2f[1] = v[0];
2415 			t2f[2] = tex->s1;t2f[3] = v[0];
2416 			t2f[4] = tex->s1;t2f[5] = v[1];
2417 			t2f[6] = tex->s2;t2f[7] = v[1];
2418 			break;
2419 		case PARTICLE_HBEAM:
2420 			R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2421 			VectorSubtract(p->vel, p->org, up);
2422 			VectorNormalize(up);
2423 			v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2424 			v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2425 			t2f[0] = v[0];t2f[1] = tex->t1;
2426 			t2f[2] = v[0];t2f[3] = tex->t2;
2427 			t2f[4] = v[1];t2f[5] = tex->t2;
2428 			t2f[6] = v[1];t2f[7] = tex->t1;
2429 			break;
2430 		}
2431 	}
2432 
2433 	// now render batches of particles based on blendmode and texture
2434 	blendmode = PBLEND_INVALID;
2435 	texture = NULL;
2436 	GL_LockArrays(0, numsurfaces*4);
2437 	batchstart = 0;
2438 	batchcount = 0;
2439 	for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2440 	{
2441 		p = cl.particles + surfacelist[surfacelistindex];
2442 
2443 		if (blendmode != p->blendmode)
2444 		{
2445 			blendmode = p->blendmode;
2446 			switch(blendmode)
2447 			{
2448 			case PBLEND_ALPHA:
2449 				GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2450 				break;
2451 			case PBLEND_INVALID:
2452 			case PBLEND_ADD:
2453 				GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2454 				break;
2455 			case PBLEND_INVMOD:
2456 				GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2457 				break;
2458 			}
2459 		}
2460 		if (texture != particletexture[p->texnum].texture)
2461 		{
2462 			texture = particletexture[p->texnum].texture;
2463 			R_Mesh_TexBind(0, R_GetTexture(texture));
2464 		}
2465 
2466 		// iterate until we find a change in settings
2467 		batchstart = surfacelistindex++;
2468 		for (;surfacelistindex < numsurfaces;surfacelistindex++)
2469 		{
2470 			p = cl.particles + surfacelist[surfacelistindex];
2471 			if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2472 				break;
2473 		}
2474 
2475 		batchcount = surfacelistindex - batchstart;
2476 		R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2477 	}
2478 	GL_LockArrays(0, 0);
2479 }
2480 
R_DrawParticles(void)2481 void R_DrawParticles (void)
2482 {
2483 	int i, a, content;
2484 	float minparticledist;
2485 	particle_t *p;
2486 	float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2487 	float drawdist2;
2488 	int hitent;
2489 	trace_t trace;
2490 	qboolean update;
2491 
2492 	frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2493 	cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2494 
2495 	// LordHavoc: early out conditions
2496 	if ((!cl.num_particles) || (!r_drawparticles.integer))
2497 		return;
2498 
2499 	minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2500 	gravity = frametime * cl.movevars_gravity;
2501 	dvel = 1+4*frametime;
2502 	decalfade = frametime * 255 / cl_decals_fadetime.value;
2503 	update = frametime > 0;
2504 	drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2505 	drawdist2 = drawdist2*drawdist2;
2506 
2507 	for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2508 	{
2509 		if (!p->typeindex)
2510 		{
2511 			if (cl.free_particle > i)
2512 				cl.free_particle = i;
2513 			continue;
2514 		}
2515 
2516 		if (update)
2517 		{
2518 			if (p->delayedspawn > cl.time)
2519 				continue;
2520 			p->delayedspawn = 0;
2521 
2522 			content = 0;
2523 
2524 			p->size += p->sizeincrease * frametime;
2525 			p->alpha -= p->alphafade * frametime;
2526 
2527 			if (p->alpha <= 0 || p->die <= cl.time)
2528 				goto killparticle;
2529 
2530 			if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2531 			{
2532 				if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2533 				{
2534 					if (p->typeindex == pt_blood)
2535 						p->size += frametime * 8;
2536 					else
2537 						p->vel[2] -= p->gravity * gravity;
2538 					f = 1.0f - min(p->liquidfriction * frametime, 1);
2539 					VectorScale(p->vel, f, p->vel);
2540 				}
2541 				else
2542 				{
2543 					p->vel[2] -= p->gravity * gravity;
2544 					if (p->airfriction)
2545 					{
2546 						f = 1.0f - min(p->airfriction * frametime, 1);
2547 						VectorScale(p->vel, f, p->vel);
2548 					}
2549 				}
2550 
2551 				VectorCopy(p->org, oldorg);
2552 				VectorMA(p->org, frametime, p->vel, p->org);
2553 				if (p->bounce && cl.time >= p->delayedcollisions)
2554 				{
2555 					trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
2556 					// if the trace started in or hit something of SUPERCONTENTS_NODROP
2557 					// or if the trace hit something flagged as NOIMPACT
2558 					// then remove the particle
2559 					if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2560 						goto killparticle;
2561 					VectorCopy(trace.endpos, p->org);
2562 					// react if the particle hit something
2563 					if (trace.fraction < 1)
2564 					{
2565 						VectorCopy(trace.endpos, p->org);
2566 
2567 						if (p->staintexnum >= 0)
2568 						{
2569 							// blood - splash on solid
2570 							if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2571 							{
2572 								R_Stain(p->org, 16,
2573 									(p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2574 									(p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2575 								if (cl_decals.integer)
2576 								{
2577 									// create a decal for the blood splat
2578 									CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, 0xFFFFFF ^ p->staincolor, 0xFFFFFF ^ p->staincolor, p->staintexnum, p->size * 2, p->alpha); // staincolor needs to be inverted for decals!
2579 								}
2580 							}
2581 						}
2582 
2583 						if (p->typeindex == pt_blood)
2584 						{
2585 							// blood - splash on solid
2586 							if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2587 								goto killparticle;
2588 							if(p->staintexnum == -1) // staintex < -1 means no stains at all
2589 							{
2590 								R_Stain(p->org, 16, 64, 16, 16, (int)(p->alpha * p->size * (1.0f / 80.0f)), 64, 32, 32, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2591 								if (cl_decals.integer)
2592 								{
2593 									// create a decal for the blood splat
2594 									CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * 2, p->alpha);
2595 								}
2596 							}
2597 							goto killparticle;
2598 						}
2599 						else if (p->bounce < 0)
2600 						{
2601 							// bounce -1 means remove on impact
2602 							goto killparticle;
2603 						}
2604 						else
2605 						{
2606 							// anything else - bounce off solid
2607 							dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2608 							VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2609 							if (DotProduct(p->vel, p->vel) < 0.03)
2610 								VectorClear(p->vel);
2611 						}
2612 					}
2613 				}
2614 			}
2615 
2616 			if (p->typeindex != pt_static)
2617 			{
2618 				switch (p->typeindex)
2619 				{
2620 				case pt_entityparticle:
2621 					// particle that removes itself after one rendered frame
2622 					if (p->time2)
2623 						goto killparticle;
2624 					else
2625 						p->time2 = 1;
2626 					break;
2627 				case pt_blood:
2628 					a = CL_PointSuperContents(p->org);
2629 					if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2630 						goto killparticle;
2631 					break;
2632 				case pt_bubble:
2633 					a = CL_PointSuperContents(p->org);
2634 					if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2635 						goto killparticle;
2636 					break;
2637 				case pt_rain:
2638 					a = CL_PointSuperContents(p->org);
2639 					if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2640 						goto killparticle;
2641 					break;
2642 				case pt_snow:
2643 					if (cl.time > p->time2)
2644 					{
2645 						// snow flutter
2646 						p->time2 = cl.time + (rand() & 3) * 0.1;
2647 						p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2648 						p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2649 					}
2650 					a = CL_PointSuperContents(p->org);
2651 					if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2652 						goto killparticle;
2653 					break;
2654 				default:
2655 					break;
2656 				}
2657 			}
2658 		}
2659 		else if (p->delayedspawn)
2660 			continue;
2661 
2662 		// don't render particles too close to the view (they chew fillrate)
2663 		// also don't render particles behind the view (useless)
2664 		// further checks to cull to the frustum would be too slow here
2665 		switch(p->typeindex)
2666 		{
2667 		case pt_beam:
2668 			// beams have no culling
2669 			R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2670 			break;
2671 		default:
2672 			if(cl_particles_visculling.integer)
2673 				if (!r_refdef.viewcache.world_novis)
2674 					if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2675 					{
2676 						mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2677 						if(leaf)
2678 							if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2679 								continue;
2680 					}
2681 			// anything else just has to be in front of the viewer and visible at this distance
2682 			if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2683 				R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2684 			break;
2685 		}
2686 
2687 		continue;
2688 killparticle:
2689 		p->typeindex = 0;
2690 		if (cl.free_particle > i)
2691 			cl.free_particle = i;
2692 	}
2693 
2694 	// reduce cl.num_particles if possible
2695 	while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2696 		cl.num_particles--;
2697 
2698 	if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2699 	{
2700 		particle_t *oldparticles = cl.particles;
2701 		cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2702 		cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2703 		memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2704 		Mem_Free(oldparticles);
2705 	}
2706 }
2707