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