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