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