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