]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_particles.c
Treat attempts to draw trails with pointparticles as zero length trails.
[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_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)
920 {
921         vec3_t center;
922         matrix4x4_t tempmatrix;
923         particle_t *part;
924
925         VectorLerp(originmins, 0.5, originmaxs, center);
926         Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
927         if (effectnameindex == EFFECT_SVC_PARTICLE)
928         {
929                 if (cl_particles.integer)
930                 {
931                         // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
932                         if (count == 1024)
933                                 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
934                         else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
935                                 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
936                         else
937                         {
938                                 count *= cl_particles_quality.value;
939                                 for (;count > 0;count--)
940                                 {
941                                         int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
942                                         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);
943                                 }
944                         }
945                 }
946         }
947         else if (effectnameindex == EFFECT_TE_WIZSPIKE)
948                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
949         else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
950                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
951         else if (effectnameindex == EFFECT_TE_SPIKE)
952         {
953                 if (cl_particles_bulletimpacts.integer)
954                 {
955                         if (cl_particles_quake.integer)
956                         {
957                                 if (cl_particles_smoke.integer)
958                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
959                         }
960                         else
961                         {
962                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
963                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
964                                 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);
965                         }
966                 }
967                 // bullet hole
968                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
969                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
970         }
971         else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
972         {
973                 if (cl_particles_bulletimpacts.integer)
974                 {
975                         if (cl_particles_quake.integer)
976                         {
977                                 if (cl_particles_smoke.integer)
978                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
979                         }
980                         else
981                         {
982                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
983                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
984                                 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);
985                         }
986                 }
987                 // bullet hole
988                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
989                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
990                 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);
991         }
992         else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
993         {
994                 if (cl_particles_bulletimpacts.integer)
995                 {
996                         if (cl_particles_quake.integer)
997                         {
998                                 if (cl_particles_smoke.integer)
999                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1000                         }
1001                         else
1002                         {
1003                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1004                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1005                                 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);
1006                         }
1007                 }
1008                 // bullet hole
1009                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1010                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1011         }
1012         else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1013         {
1014                 if (cl_particles_bulletimpacts.integer)
1015                 {
1016                         if (cl_particles_quake.integer)
1017                         {
1018                                 if (cl_particles_smoke.integer)
1019                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1020                         }
1021                         else
1022                         {
1023                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1024                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1025                                 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);
1026                         }
1027                 }
1028                 // bullet hole
1029                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1030                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1031                 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);
1032         }
1033         else if (effectnameindex == EFFECT_TE_BLOOD)
1034         {
1035                 if (!cl_particles_blood.integer)
1036                         return;
1037                 if (cl_particles_quake.integer)
1038                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
1039                 else
1040                 {
1041                         static double bloodaccumulator = 0;
1042                         qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1043                         //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);
1044                         bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1045                         for (;bloodaccumulator > 0;bloodaccumulator--)
1046                         {
1047                                 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);
1048                                 if (immediatebloodstain && part)
1049                                 {
1050                                         immediatebloodstain = false;
1051                                         CL_ImmediateBloodStain(part);
1052                                 }
1053                         }
1054                 }
1055         }
1056         else if (effectnameindex == EFFECT_TE_SPARK)
1057                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1058         else if (effectnameindex == EFFECT_TE_PLASMABURN)
1059         {
1060                 // plasma scorch mark
1061                 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1062                 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1063                 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1064         }
1065         else if (effectnameindex == EFFECT_TE_GUNSHOT)
1066         {
1067                 if (cl_particles_bulletimpacts.integer)
1068                 {
1069                         if (cl_particles_quake.integer)
1070                                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1071                         else
1072                         {
1073                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1074                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1075                                 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);
1076                         }
1077                 }
1078                 // bullet hole
1079                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1080                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1081         }
1082         else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1083         {
1084                 if (cl_particles_bulletimpacts.integer)
1085                 {
1086                         if (cl_particles_quake.integer)
1087                                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1088                         else
1089                         {
1090                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1091                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1092                                 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);
1093                         }
1094                 }
1095                 // bullet hole
1096                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1097                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1098                 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);
1099         }
1100         else if (effectnameindex == EFFECT_TE_EXPLOSION)
1101         {
1102                 CL_ParticleExplosion(center);
1103                 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);
1104         }
1105         else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1106         {
1107                 CL_ParticleExplosion(center);
1108                 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);
1109         }
1110         else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1111         {
1112                 if (cl_particles_quake.integer)
1113                 {
1114                         int i;
1115                         for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1116                         {
1117                                 if (i & 1)
1118                                         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);
1119                                 else
1120                                         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);
1121                         }
1122                 }
1123                 else
1124                         CL_ParticleExplosion(center);
1125                 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);
1126         }
1127         else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1128                 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);
1129         else if (effectnameindex == EFFECT_TE_FLAMEJET)
1130         {
1131                 count *= cl_particles_quality.value;
1132                 while (count-- > 0)
1133                         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);
1134         }
1135         else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1136         {
1137                 float i, j, inc, vel;
1138                 vec3_t dir, org;
1139
1140                 inc = 8 / cl_particles_quality.value;
1141                 for (i = -128;i < 128;i += inc)
1142                 {
1143                         for (j = -128;j < 128;j += inc)
1144                         {
1145                                 dir[0] = j + lhrandom(0, inc);
1146                                 dir[1] = i + lhrandom(0, inc);
1147                                 dir[2] = 256;
1148                                 org[0] = center[0] + dir[0];
1149                                 org[1] = center[1] + dir[1];
1150                                 org[2] = center[2] + lhrandom(0, 64);
1151                                 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1152                                 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);
1153                         }
1154                 }
1155         }
1156         else if (effectnameindex == EFFECT_TE_TELEPORT)
1157         {
1158                 float i, j, k, inc, vel;
1159                 vec3_t dir;
1160
1161                 if (cl_particles_quake.integer)
1162                         inc = 4 / cl_particles_quality.value;
1163                 else
1164                         inc = 8 / cl_particles_quality.value;
1165                 for (i = -16;i < 16;i += inc)
1166                 {
1167                         for (j = -16;j < 16;j += inc)
1168                         {
1169                                 for (k = -24;k < 32;k += inc)
1170                                 {
1171                                         VectorSet(dir, i*8, j*8, k*8);
1172                                         VectorNormalize(dir);
1173                                         vel = lhrandom(50, 113);
1174                                         if (cl_particles_quake.integer)
1175                                                 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);
1176                                         else
1177                                                 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);
1178                                 }
1179                         }
1180                 }
1181                 if (!cl_particles_quake.integer)
1182                         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);
1183                 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);
1184         }
1185         else if (effectnameindex == EFFECT_TE_TEI_G3)
1186                 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);
1187         else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1188         {
1189                 if (cl_particles_smoke.integer)
1190                 {
1191                         count *= 0.25f * cl_particles_quality.value;
1192                         while (count-- > 0)
1193                                 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);
1194                 }
1195         }
1196         else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1197         {
1198                 CL_ParticleExplosion(center);
1199                 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);
1200         }
1201         else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1202         {
1203                 float f;
1204                 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1205                 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1206                 if (cl_particles_smoke.integer)
1207                         for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1208                                 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);
1209                 if (cl_particles_sparks.integer)
1210                         for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1211                                 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);
1212                 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);
1213         }
1214         else if (effectnameindex == EFFECT_EF_FLAME)
1215         {
1216                 if (!spawnparticles)
1217                         count = 0;
1218                 count *= 300 * cl_particles_quality.value;
1219                 while (count-- > 0)
1220                         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);
1221                 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);
1222         }
1223         else if (effectnameindex == EFFECT_EF_STARDUST)
1224         {
1225                 if (!spawnparticles)
1226                         count = 0;
1227                 count *= 200 * cl_particles_quality.value;
1228                 while (count-- > 0)
1229                         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);
1230                 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);
1231         }
1232         else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1233         {
1234                 vec3_t dir, pos;
1235                 float len, dec, qd;
1236                 int smoke, blood, bubbles, r, color;
1237
1238                 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1239                 {
1240                         vec4_t light;
1241                         Vector4Set(light, 0, 0, 0, 0);
1242
1243                         if (effectnameindex == EFFECT_TR_ROCKET)
1244                                 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1245                         else if (effectnameindex == EFFECT_TR_VORESPIKE)
1246                         {
1247                                 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1248                                         Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1249                                 else
1250                                         Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1251                         }
1252                         else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1253                                 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1254
1255                         if (light[3])
1256                         {
1257                                 matrix4x4_t tempmatrix;
1258                                 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1259                                 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);
1260                                 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1261                         }
1262                 }
1263
1264                 if (!spawnparticles)
1265                         return;
1266
1267                 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1268                         return;
1269
1270                 VectorSubtract(originmaxs, originmins, dir);
1271                 len = VectorNormalizeLength(dir);
1272                 if (ent)
1273                 {
1274                         dec = -ent->persistent.trail_time;
1275                         ent->persistent.trail_time += len;
1276                         if (ent->persistent.trail_time < 0.01f)
1277                                 return;
1278
1279                         // if we skip out, leave it reset
1280                         ent->persistent.trail_time = 0.0f;
1281                 }
1282                 else
1283                         dec = 0;
1284
1285                 // advance into this frame to reach the first puff location
1286                 VectorMA(originmins, dec, dir, pos);
1287                 len -= dec;
1288
1289                 smoke = cl_particles.integer && cl_particles_smoke.integer;
1290                 blood = cl_particles.integer && cl_particles_blood.integer;
1291                 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1292                 qd = 1.0f / cl_particles_quality.value;
1293
1294                 while (len >= 0)
1295                 {
1296                         dec = 3;
1297                         if (blood)
1298                         {
1299                                 if (effectnameindex == EFFECT_TR_BLOOD)
1300                                 {
1301                                         if (cl_particles_quake.integer)
1302                                         {
1303                                                 color = particlepalette[67 + (rand()&3)];
1304                                                 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);
1305                                         }
1306                                         else
1307                                         {
1308                                                 dec = 16;
1309                                                 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);
1310                                         }
1311                                 }
1312                                 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1313                                 {
1314                                         if (cl_particles_quake.integer)
1315                                         {
1316                                                 dec = 6;
1317                                                 color = particlepalette[67 + (rand()&3)];
1318                                                 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);
1319                                         }
1320                                         else
1321                                         {
1322                                                 dec = 32;
1323                                                 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);
1324                                         }
1325                                 }
1326                         }
1327                         if (smoke)
1328                         {
1329                                 if (effectnameindex == EFFECT_TR_ROCKET)
1330                                 {
1331                                         if (cl_particles_quake.integer)
1332                                         {
1333                                                 r = rand()&3;
1334                                                 color = particlepalette[ramp3[r]];
1335                                                 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);
1336                                         }
1337                                         else
1338                                         {
1339                                                 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);
1340                                                 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);
1341                                         }
1342                                 }
1343                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1344                                 {
1345                                         if (cl_particles_quake.integer)
1346                                         {
1347                                                 r = 2 + (rand()%5);
1348                                                 color = particlepalette[ramp3[r]];
1349                                                 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);
1350                                         }
1351                                         else
1352                                         {
1353                                                 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);
1354                                         }
1355                                 }
1356                                 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1357                                 {
1358                                         if (cl_particles_quake.integer)
1359                                         {
1360                                                 dec = 6;
1361                                                 color = particlepalette[52 + (rand()&7)];
1362                                                 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);
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                                         }
1365                                         else if (gamemode == GAME_GOODVSBAD2)
1366                                         {
1367                                                 dec = 6;
1368                                                 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);
1369                                         }
1370                                         else
1371                                         {
1372                                                 color = particlepalette[20 + (rand()&7)];
1373                                                 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);
1374                                         }
1375                                 }
1376                                 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1377                                 {
1378                                         if (cl_particles_quake.integer)
1379                                         {
1380                                                 dec = 6;
1381                                                 color = particlepalette[230 + (rand()&7)];
1382                                                 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);
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                                         }
1385                                         else
1386                                         {
1387                                                 color = particlepalette[226 + (rand()&7)];
1388                                                 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);
1389                                         }
1390                                 }
1391                                 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1392                                 {
1393                                         if (cl_particles_quake.integer)
1394                                         {
1395                                                 color = particlepalette[152 + (rand()&3)];
1396                                                 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);
1397                                         }
1398                                         else if (gamemode == GAME_GOODVSBAD2)
1399                                         {
1400                                                 dec = 6;
1401                                                 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);
1402                                         }
1403                                         else if (gamemode == GAME_PRYDON)
1404                                         {
1405                                                 dec = 6;
1406                                                 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);
1407                                         }
1408                                         else
1409                                                 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);
1410                                 }
1411                                 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1412                                 {
1413                                         dec = 7;
1414                                         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);
1415                                 }
1416                                 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1417                                 {
1418                                         dec = 4;
1419                                         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);
1420                                 }
1421                                 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1422                                         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);
1423                         }
1424                         if (bubbles)
1425                         {
1426                                 if (effectnameindex == EFFECT_TR_ROCKET)
1427                                         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);
1428                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1429                                         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);
1430                         }
1431                         // advance to next time and position
1432                         dec *= qd;
1433                         len -= dec;
1434                         VectorMA (pos, dec, dir, pos);
1435                 }
1436                 if (ent)
1437                         ent->persistent.trail_time = len;
1438         }
1439         else
1440                 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1441 }
1442
1443 // this is also called on point effects with spawndlight = true and
1444 // spawnparticles = true
1445 // it is called CL_ParticleTrail because most code does not want to supply
1446 // these parameters, only trail handling does
1447 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)
1448 {
1449         qboolean found = false;
1450         char vabuf[1024];
1451         if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1452         {
1453                 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1454                 return; // no such effect
1455         }
1456         if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1457         {
1458                 int effectinfoindex;
1459                 int supercontents;
1460                 int tex, staintex;
1461                 particleeffectinfo_t *info;
1462                 vec3_t center;
1463                 vec3_t traildir;
1464                 vec3_t trailpos;
1465                 vec3_t rvec;
1466                 vec3_t angles;
1467                 vec3_t velocity;
1468                 vec3_t forward;
1469                 vec3_t right;
1470                 vec3_t up;
1471                 vec_t traillen;
1472                 vec_t trailstep;
1473                 qboolean underwater;
1474                 qboolean immediatebloodstain;
1475                 particle_t *part;
1476                 float avgtint[4], tint[4], tintlerp;
1477                 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1478                 VectorLerp(originmins, 0.5, originmaxs, center);
1479                 supercontents = CL_PointSuperContents(center);
1480                 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1481                 VectorSubtract(originmaxs, originmins, traildir);
1482                 traillen = VectorLength(traildir);
1483                 VectorNormalize(traildir);
1484                 if(tintmins)
1485                 {
1486                         Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1487                 }
1488                 else
1489                 {
1490                         Vector4Set(avgtint, 1, 1, 1, 1);
1491                 }
1492                 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1493                 {
1494                         if (info->effectnameindex == effectnameindex)
1495                         {
1496                                 qboolean definedastrail = info->trailspacing > 0;
1497
1498                                 qboolean drawastrail = wanttrail;
1499                                 if (cl_particles_forcetraileffects.integer)
1500                                         drawastrail = drawastrail || definedastrail;
1501
1502                                 found = true;
1503                                 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1504                                         continue;
1505                                 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1506                                         continue;
1507
1508                                 // spawn a dlight if requested
1509                                 if (info->lightradiusstart > 0 && spawndlight)
1510                                 {
1511                                         matrix4x4_t tempmatrix;
1512                                         if (drawastrail)
1513                                                 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1514                                         else
1515                                                 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1516                                         if (info->lighttime > 0 && info->lightradiusfade > 0)
1517                                         {
1518                                                 // light flash (explosion, etc)
1519                                                 // called when effect starts
1520                                                 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);
1521                                         }
1522                                         else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1523                                         {
1524                                                 // glowing entity
1525                                                 // called by CL_LinkNetworkEntity
1526                                                 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1527                                                 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1528                                                 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1529                                                 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1530                                                 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);
1531                                                 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1532                                         }
1533                                 }
1534
1535                                 if (!spawnparticles)
1536                                         continue;
1537
1538                                 // spawn particles
1539                                 tex = info->tex[0];
1540                                 if (info->tex[1] > info->tex[0])
1541                                 {
1542                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1543                                         tex = min(tex, info->tex[1] - 1);
1544                                 }
1545                                 if(info->staintex[0] < 0)
1546                                         staintex = info->staintex[0];
1547                                 else
1548                                 {
1549                                         staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1550                                         staintex = min(staintex, info->staintex[1] - 1);
1551                                 }
1552                                 if (info->particletype == pt_decal)
1553                                 {
1554                                         VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1555                                         AnglesFromVectors(angles, velocity, NULL, false);
1556                                         AngleVectors(angles, forward, right, up);
1557                                         VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1558
1559                                         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]);
1560                                 }
1561                                 else if (info->orientation == PARTICLE_HBEAM)
1562                                 {
1563                                         if (!drawastrail)
1564                                                 continue;
1565
1566                                         AnglesFromVectors(angles, traildir, NULL, false);
1567                                         AngleVectors(angles, forward, right, up);
1568                                         VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1569
1570                                         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);
1571                                 }
1572                                 else
1573                                 {
1574                                         float cnt;
1575                                         if (!cl_particles.integer)
1576                                                 continue;
1577                                         switch (info->particletype)
1578                                         {
1579                                         case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1580                                         case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1581                                         case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1582                                         case pt_blood: if (!cl_particles_blood.integer) continue;break;
1583                                         case pt_rain: if (!cl_particles_rain.integer) continue;break;
1584                                         case pt_snow: if (!cl_particles_snow.integer) continue;break;
1585                                         default: break;
1586                                         }
1587
1588                                         cnt = info->countabsolute;
1589                                         cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1590                                         // if drawastrail is not set, we will
1591                                         // use the regular cnt-based random
1592                                         // particle spawning at the center; so
1593                                         // do NOT apply trailspacing then!
1594                                         if (drawastrail && definedastrail)
1595                                                 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1596                                         cnt *= fade;
1597                                         if (cnt == 0)
1598                                                 continue;  // nothing to draw
1599                                         info->particleaccumulator += cnt;
1600
1601                                         if (drawastrail || definedastrail)
1602                                                 immediatebloodstain = false;
1603                                         else
1604                                                 immediatebloodstain =
1605                                                         ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1606                                                         ||
1607                                                         ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1608
1609                                         if (drawastrail)
1610                                         {
1611                                                 VectorCopy(originmins, trailpos);
1612                                                 trailstep = traillen / cnt;
1613                                                 AnglesFromVectors(angles, traildir, NULL, false);
1614                                         }
1615                                         else
1616                                         {
1617                                                 VectorCopy(center, trailpos);
1618                                                 trailstep = 0;
1619                                                 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1620                                                 AnglesFromVectors(angles, velocity, NULL, false);
1621                                         }
1622
1623                                         AngleVectors(angles, forward, right, up);
1624                                         VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1625                                         VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1626                                         info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1627                                         for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1628                                         {
1629                                                 if (info->tex[1] > info->tex[0])
1630                                                 {
1631                                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1632                                                         tex = min(tex, info->tex[1] - 1);
1633                                                 }
1634                                                 if (!(drawastrail || definedastrail))
1635                                                 {
1636                                                         trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1637                                                         trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1638                                                         trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1639                                                 }
1640                                                 if(tintmins)
1641                                                 {
1642                                                         tintlerp = lhrandom(0, 1);
1643                                                         Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1644                                                 }
1645                                                 VectorRandom(rvec);
1646                                                 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);
1647                                                 if (immediatebloodstain && part)
1648                                                 {
1649                                                         immediatebloodstain = false;
1650                                                         CL_ImmediateBloodStain(part);
1651                                                 }
1652                                                 if (trailstep)
1653                                                         VectorMA(trailpos, trailstep, traildir, trailpos);
1654                                         }
1655                                 }
1656                         }
1657                 }
1658         }
1659         if (!found)
1660                 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1661 }
1662
1663 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)
1664 {
1665         CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1666 }
1667
1668 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)
1669 {
1670         CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1671 }
1672
1673 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)
1674 {
1675         CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1676 }
1677
1678 /*
1679 ===============
1680 CL_EntityParticles
1681 ===============
1682 */
1683 void CL_EntityParticles (const entity_t *ent)
1684 {
1685         int i, j;
1686         vec_t pitch, yaw, dist = 64, beamlength = 16;
1687         vec3_t org, v;
1688         static vec3_t avelocities[NUMVERTEXNORMALS];
1689         if (!cl_particles.integer) return;
1690         if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1691
1692         Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1693
1694         if (!avelocities[0][0])
1695                 for (i = 0;i < NUMVERTEXNORMALS;i++)
1696                         for (j = 0;j < 3;j++)
1697                                 avelocities[i][j] = lhrandom(0, 2.55);
1698
1699         for (i = 0;i < NUMVERTEXNORMALS;i++)
1700         {
1701                 yaw = cl.time * avelocities[i][0];
1702                 pitch = cl.time * avelocities[i][1];
1703                 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1704                 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1705                 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1706                 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);
1707         }
1708 }
1709
1710
1711 void CL_ReadPointFile_f (void)
1712 {
1713         double org[3], leakorg[3];
1714         vec3_t vecorg;
1715         int r, c, s;
1716         char *pointfile = NULL, *pointfilepos, *t, tchar;
1717         char name[MAX_QPATH];
1718
1719         if (!cl.worldmodel)
1720                 return;
1721
1722         dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1723         pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1724         if (!pointfile)
1725         {
1726                 Con_Printf("Could not open %s\n", name);
1727                 return;
1728         }
1729
1730         Con_Printf("Reading %s...\n", name);
1731         VectorClear(leakorg);
1732         c = 0;
1733         s = 0;
1734         pointfilepos = pointfile;
1735         while (*pointfilepos)
1736         {
1737                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1738                         pointfilepos++;
1739                 if (!*pointfilepos)
1740                         break;
1741                 t = pointfilepos;
1742                 while (*t && *t != '\n' && *t != '\r')
1743                         t++;
1744                 tchar = *t;
1745                 *t = 0;
1746 #if _MSC_VER >= 1400
1747 #define sscanf sscanf_s
1748 #endif
1749                 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1750                 VectorCopy(org, vecorg);
1751                 *t = tchar;
1752                 pointfilepos = t;
1753                 if (r != 3)
1754                         break;
1755                 if (c == 0)
1756                         VectorCopy(org, leakorg);
1757                 c++;
1758
1759                 if (cl.num_particles < cl.max_particles - 3)
1760                 {
1761                         s++;
1762                         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);
1763                 }
1764         }
1765         Mem_Free(pointfile);
1766         VectorCopy(leakorg, vecorg);
1767         Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1768
1769         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);
1770         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);
1771         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);
1772 }
1773
1774 /*
1775 ===============
1776 CL_ParseParticleEffect
1777
1778 Parse an effect out of the server message
1779 ===============
1780 */
1781 void CL_ParseParticleEffect (void)
1782 {
1783         vec3_t org, dir;
1784         int i, count, msgcount, color;
1785
1786         MSG_ReadVector(&cl_message, org, cls.protocol);
1787         for (i=0 ; i<3 ; i++)
1788                 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1789         msgcount = MSG_ReadByte(&cl_message);
1790         color = MSG_ReadByte(&cl_message);
1791
1792         if (msgcount == 255)
1793                 count = 1024;
1794         else
1795                 count = msgcount;
1796
1797         CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1798 }
1799
1800 /*
1801 ===============
1802 CL_ParticleExplosion
1803
1804 ===============
1805 */
1806 void CL_ParticleExplosion (const vec3_t org)
1807 {
1808         int i;
1809         trace_t trace;
1810         //vec3_t v;
1811         //vec3_t v2;
1812         R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1813         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1814
1815         if (cl_particles_quake.integer)
1816         {
1817                 for (i = 0;i < 1024;i++)
1818                 {
1819                         int r, color;
1820                         r = rand()&3;
1821                         if (i & 1)
1822                         {
1823                                 color = particlepalette[ramp1[r]];
1824                                 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);
1825                         }
1826                         else
1827                         {
1828                                 color = particlepalette[ramp2[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, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1830                         }
1831                 }
1832         }
1833         else
1834         {
1835                 i = CL_PointSuperContents(org);
1836                 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1837                 {
1838                         if (cl_particles.integer && cl_particles_bubbles.integer)
1839                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1840                                         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);
1841                 }
1842                 else
1843                 {
1844                         if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1845                         {
1846                                 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1847                                 {
1848                                         int k = 0;
1849                                         vec3_t v, v2;
1850                                         do
1851                                         {
1852                                                 VectorRandom(v2);
1853                                                 VectorMA(org, 128, v2, v);
1854                                                 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1855                                         }
1856                                         while (k < 16 && trace.fraction < 0.1f);
1857                                         VectorSubtract(trace.endpos, org, v2);
1858                                         VectorScale(v2, 2.0f, v2);
1859                                         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);
1860                                 }
1861                         }
1862                 }
1863         }
1864
1865         if (cl_particles_explosions_shell.integer)
1866                 R_NewExplosion(org);
1867 }
1868
1869 /*
1870 ===============
1871 CL_ParticleExplosion2
1872
1873 ===============
1874 */
1875 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1876 {
1877         int i, k;
1878         if (!cl_particles.integer) return;
1879
1880         for (i = 0;i < 512 * cl_particles_quality.value;i++)
1881         {
1882                 k = particlepalette[colorStart + (i % colorLength)];
1883                 if (cl_particles_quake.integer)
1884                         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);
1885                 else
1886                         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);
1887         }
1888 }
1889
1890 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1891 {
1892         vec3_t center;
1893         VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1894         if (cl_particles_sparks.integer)
1895         {
1896                 sparkcount *= cl_particles_quality.value;
1897                 while(sparkcount-- > 0)
1898                         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);
1899         }
1900 }
1901
1902 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1903 {
1904         vec3_t center;
1905         VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1906         if (cl_particles_smoke.integer)
1907         {
1908                 smokecount *= cl_particles_quality.value;
1909                 while(smokecount-- > 0)
1910                         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);
1911         }
1912 }
1913
1914 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)
1915 {
1916         vec3_t center;
1917         int k;
1918         if (!cl_particles.integer) return;
1919         VectorMAM(0.5f, mins, 0.5f, maxs, center);
1920
1921         count = (int)(count * cl_particles_quality.value);
1922         while (count--)
1923         {
1924                 k = particlepalette[colorbase + (rand()&3)];
1925                 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);
1926         }
1927 }
1928
1929 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1930 {
1931         int k;
1932         float minz, maxz, lifetime = 30;
1933         vec3_t org;
1934         if (!cl_particles.integer) return;
1935         if (dir[2] < 0) // falling
1936         {
1937                 minz = maxs[2] + dir[2] * 0.1;
1938                 maxz = maxs[2];
1939                 if (cl.worldmodel)
1940                         lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1941         }
1942         else // rising??
1943         {
1944                 minz = mins[2];
1945                 maxz = maxs[2] + dir[2] * 0.1;
1946                 if (cl.worldmodel)
1947                         lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1948         }
1949
1950         count = (int)(count * cl_particles_quality.value);
1951
1952         switch(type)
1953         {
1954         case 0:
1955                 if (!cl_particles_rain.integer) break;
1956                 count *= 4; // ick, this should be in the mod or maps?
1957
1958                 while(count--)
1959                 {
1960                         k = particlepalette[colorbase + (rand()&3)];
1961                         VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1962                         if (gamemode == GAME_GOODVSBAD2)
1963                                 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);
1964                         else
1965                                 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);
1966                 }
1967                 break;
1968         case 1:
1969                 if (!cl_particles_snow.integer) break;
1970                 while(count--)
1971                 {
1972                         k = particlepalette[colorbase + (rand()&3)];
1973                         VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1974                         if (gamemode == GAME_GOODVSBAD2)
1975                                 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);
1976                         else
1977                                 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);
1978                 }
1979                 break;
1980         default:
1981                 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1982         }
1983 }
1984
1985 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1986 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1987 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1988 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1989 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1990 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1991
1992 #define PARTICLETEXTURESIZE 64
1993 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1994
1995 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1996 {
1997         float dz, f, dot;
1998         vec3_t normal;
1999         dz = 1 - (dx*dx+dy*dy);
2000         if (dz > 0) // it does hit the sphere
2001         {
2002                 f = 0;
2003                 // back side
2004                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
2005                 VectorNormalize(normal);
2006                 dot = DotProduct(normal, light);
2007                 if (dot > 0.5) // interior reflection
2008                         f += ((dot *  2) - 1);
2009                 else if (dot < -0.5) // exterior reflection
2010                         f += ((dot * -2) - 1);
2011                 // front side
2012                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
2013                 VectorNormalize(normal);
2014                 dot = DotProduct(normal, light);
2015                 if (dot > 0.5) // interior reflection
2016                         f += ((dot *  2) - 1);
2017                 else if (dot < -0.5) // exterior reflection
2018                         f += ((dot * -2) - 1);
2019                 f *= 128;
2020                 f += 16; // just to give it a haze so you can see the outline
2021                 f = bound(0, f, 255);
2022                 return (unsigned char) f;
2023         }
2024         else
2025                 return 0;
2026 }
2027
2028 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
2029 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
2030 {
2031         *basex = (texnum % particlefontcols) * particlefontcellwidth;
2032         *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
2033         *width = particlefontcellwidth;
2034         *height = particlefontcellheight;
2035 }
2036
2037 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
2038 {
2039         int basex, basey, w, h, y;
2040         CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2041         if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2042                 Sys_Error("invalid particle texture size for autogenerating");
2043         for (y = 0;y < PARTICLETEXTURESIZE;y++)
2044                 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2045 }
2046
2047 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2048 {
2049         int x, y;
2050         float cx, cy, dx, dy, f, iradius;
2051         unsigned char *d;
2052         cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2053         cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2054         iradius = 1.0f / radius;
2055         alpha *= (1.0f / 255.0f);
2056         for (y = 0;y < PARTICLETEXTURESIZE;y++)
2057         {
2058                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2059                 {
2060                         dx = (x - cx);
2061                         dy = (y - cy);
2062                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2063                         if (f > 0)
2064                         {
2065                                 if (f > 1)
2066                                         f = 1;
2067                                 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2068                                 d[0] += (int)(f * (blue  - d[0]));
2069                                 d[1] += (int)(f * (green - d[1]));
2070                                 d[2] += (int)(f * (red   - d[2]));
2071                         }
2072                 }
2073         }
2074 }
2075
2076 #if 0
2077 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2078 {
2079         int i;
2080         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2081         {
2082                 data[0] = bound(minb, data[0], maxb);
2083                 data[1] = bound(ming, data[1], maxg);
2084                 data[2] = bound(minr, data[2], maxr);
2085         }
2086 }
2087 #endif
2088
2089 static void particletextureinvert(unsigned char *data)
2090 {
2091         int i;
2092         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2093         {
2094                 data[0] = 255 - data[0];
2095                 data[1] = 255 - data[1];
2096                 data[2] = 255 - data[2];
2097         }
2098 }
2099
2100 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2101 static void R_InitBloodTextures (unsigned char *particletexturedata)
2102 {
2103         int i, j, k, m;
2104         size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2105         unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2106
2107         // blood particles
2108         for (i = 0;i < 8;i++)
2109         {
2110                 memset(data, 255, datasize);
2111                 for (k = 0;k < 24;k++)
2112                         particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2113                 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2114                 particletextureinvert(data);
2115                 setuptex(tex_bloodparticle[i], data, particletexturedata);
2116         }
2117
2118         // blood decals
2119         for (i = 0;i < 8;i++)
2120         {
2121                 memset(data, 255, datasize);
2122                 m = 8;
2123                 for (j = 1;j < 10;j++)
2124                         for (k = min(j, m - 1);k < m;k++)
2125                                 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2126                 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2127                 particletextureinvert(data);
2128                 setuptex(tex_blooddecal[i], data, particletexturedata);
2129         }
2130
2131         Mem_Free(data);
2132 }
2133
2134 //uncomment this to make engine save out particle font to a tga file when run
2135 //#define DUMPPARTICLEFONT
2136
2137 static void R_InitParticleTexture (void)
2138 {
2139         int x, y, d, i, k, m;
2140         int basex, basey, w, h;
2141         float dx, dy, f, s1, t1, s2, t2;
2142         vec3_t light;
2143         char *buf;
2144         fs_offset_t filesize;
2145         char texturename[MAX_QPATH];
2146         skinframe_t *sf;
2147
2148         // a note: decals need to modulate (multiply) the background color to
2149         // properly darken it (stain), and they need to be able to alpha fade,
2150         // this is a very difficult challenge because it means fading to white
2151         // (no change to background) rather than black (darkening everything
2152         // behind the whole decal polygon), and to accomplish this the texture is
2153         // inverted (dark red blood on white background becomes brilliant cyan
2154         // and white on black background) so we can alpha fade it to black, then
2155         // we invert it again during the blendfunc to make it work...
2156
2157 #ifndef DUMPPARTICLEFONT
2158         decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2159         if (decalskinframe)
2160         {
2161                 particlefonttexture = decalskinframe->base;
2162                 // TODO maybe allow custom grid size?
2163                 particlefontwidth = image_width;
2164                 particlefontheight = image_height;
2165                 particlefontcellwidth = image_width / 8;
2166                 particlefontcellheight = image_height / 8;
2167                 particlefontcols = 8;
2168                 particlefontrows = 8;
2169         }
2170         else
2171 #endif
2172         {
2173                 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2174                 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2175                 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2176                 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2177                 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2178
2179                 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2180                 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2181                 particlefontcols = 8;
2182                 particlefontrows = 8;
2183
2184                 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2185
2186                 // smoke
2187                 for (i = 0;i < 8;i++)
2188                 {
2189                         memset(data, 255, datasize);
2190                         do
2191                         {
2192                                 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2193                                 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2194                                 m = 0;
2195                                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2196                                 {
2197                                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2198                                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2199                                         {
2200                                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2201                                                 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2202                                                 if (d > 0)
2203                                                         d = (int)(d * (1-(dx*dx+dy*dy)));
2204                                                 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2205                                                 d = bound(0, d, 255);
2206                                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2207                                                 if (m < d)
2208                                                         m = d;
2209                                         }
2210                                 }
2211                         }
2212                         while (m < 224);
2213                         setuptex(tex_smoke[i], data, particletexturedata);
2214                 }
2215
2216                 // rain splash
2217                 memset(data, 255, datasize);
2218                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2219                 {
2220                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2221                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2222                         {
2223                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2224                                 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2225                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2226                         }
2227                 }
2228                 setuptex(tex_rainsplash, data, particletexturedata);
2229
2230                 // normal particle
2231                 memset(data, 255, datasize);
2232                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2233                 {
2234                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2235                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2236                         {
2237                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2238                                 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2239                                 d = bound(0, d, 255);
2240                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2241                         }
2242                 }
2243                 setuptex(tex_particle, data, particletexturedata);
2244
2245                 // rain
2246                 memset(data, 255, datasize);
2247                 light[0] = 1;light[1] = 1;light[2] = 1;
2248                 VectorNormalize(light);
2249                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2250                 {
2251                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2252                         // stretch upper half of bubble by +50% and shrink lower half by -50%
2253                         // (this gives an elongated teardrop shape)
2254                         if (dy > 0.5f)
2255                                 dy = (dy - 0.5f) * 2.0f;
2256                         else
2257                                 dy = (dy - 0.5f) / 1.5f;
2258                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2259                         {
2260                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2261                                 // shrink bubble width to half
2262                                 dx *= 2.0f;
2263                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2264                         }
2265                 }
2266                 setuptex(tex_raindrop, data, particletexturedata);
2267
2268                 // bubble
2269                 memset(data, 255, datasize);
2270                 light[0] = 1;light[1] = 1;light[2] = 1;
2271                 VectorNormalize(light);
2272                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2273                 {
2274                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2275                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2276                         {
2277                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2278                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2279                         }
2280                 }
2281                 setuptex(tex_bubble, data, particletexturedata);
2282
2283                 // Blood particles and blood decals
2284                 R_InitBloodTextures (particletexturedata);
2285
2286                 // bullet decals
2287                 for (i = 0;i < 8;i++)
2288                 {
2289                         memset(data, 255, datasize);
2290                         for (k = 0;k < 12;k++)
2291                                 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2292                         for (k = 0;k < 3;k++)
2293                                 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2294                         //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2295                         particletextureinvert(data);
2296                         setuptex(tex_bulletdecal[i], data, particletexturedata);
2297                 }
2298
2299 #ifdef DUMPPARTICLEFONT
2300                 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2301 #endif
2302
2303                 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2304                 particlefonttexture = decalskinframe->base;
2305
2306                 Mem_Free(particletexturedata);
2307                 Mem_Free(data);
2308                 Mem_Free(noise1);
2309                 Mem_Free(noise2);
2310         }
2311         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2312         {
2313                 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2314                 particletexture[i].texture = particlefonttexture;
2315                 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2316                 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2317                 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2318                 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2319         }
2320
2321 #ifndef DUMPPARTICLEFONT
2322         particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2323         if (!particletexture[tex_beam].texture)
2324 #endif
2325         {
2326                 unsigned char noise3[64][64], data2[64][16][4];
2327                 // nexbeam
2328                 fractalnoise(&noise3[0][0], 64, 4);
2329                 m = 0;
2330                 for (y = 0;y < 64;y++)
2331                 {
2332                         dy = (y - 0.5f*64) / (64*0.5f-1);
2333                         for (x = 0;x < 16;x++)
2334                         {
2335                                 dx = (x - 0.5f*16) / (16*0.5f-2);
2336                                 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2337                                 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2338                                 data2[y][x][3] = 255;
2339                         }
2340                 }
2341
2342 #ifdef DUMPPARTICLEFONT
2343                 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2344 #endif
2345                 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2346         }
2347         particletexture[tex_beam].s1 = 0;
2348         particletexture[tex_beam].t1 = 0;
2349         particletexture[tex_beam].s2 = 1;
2350         particletexture[tex_beam].t2 = 1;
2351
2352         // now load an texcoord/texture override file
2353         buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2354         if(buf)
2355         {
2356                 const char *bufptr;
2357                 bufptr = buf;
2358                 for(;;)
2359                 {
2360                         if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2361                                 break;
2362                         if(!strcmp(com_token, "\n"))
2363                                 continue; // empty line
2364                         i = atoi(com_token);
2365
2366                         texturename[0] = 0;
2367                         s1 = 0;
2368                         t1 = 0;
2369                         s2 = 1;
2370                         t2 = 1;
2371
2372                         if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2373                         {
2374                                 strlcpy(texturename, com_token, sizeof(texturename));
2375                                 s1 = atof(com_token);
2376                                 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2377                                 {
2378                                         texturename[0] = 0;
2379                                         t1 = atof(com_token);
2380                                         if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2381                                         {
2382                                                 s2 = atof(com_token);
2383                                                 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2384                                                 {
2385                                                         t2 = atof(com_token);
2386                                                         strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2387                                                         if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2388                                                                 strlcpy(texturename, com_token, sizeof(texturename));
2389                                                 }
2390                                         }
2391                                 }
2392                                 else
2393                                         s1 = 0;
2394                         }
2395                         if (!texturename[0])
2396                         {
2397                                 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2398                                 continue;
2399                         }
2400                         if (i < 0 || i >= MAX_PARTICLETEXTURES)
2401                         {
2402                                 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2403                                 continue;
2404                         }
2405                         sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2406                         if(!sf)
2407                         {
2408                                 // R_SkinFrame_LoadExternal already complained
2409                                 continue;
2410                         }
2411                         particletexture[i].texture = sf->base;
2412                         particletexture[i].s1 = s1;
2413                         particletexture[i].t1 = t1;
2414                         particletexture[i].s2 = s2;
2415                         particletexture[i].t2 = t2;
2416                 }
2417                 Mem_Free(buf);
2418         }
2419 }
2420
2421 static void r_part_start(void)
2422 {
2423         int i;
2424         // generate particlepalette for convenience from the main one
2425         for (i = 0;i < 256;i++)
2426                 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2427         particletexturepool = R_AllocTexturePool();
2428         R_InitParticleTexture ();
2429         CL_Particles_LoadEffectInfo(NULL);
2430 }
2431
2432 static void r_part_shutdown(void)
2433 {
2434         R_FreeTexturePool(&particletexturepool);
2435 }
2436
2437 static void r_part_newmap(void)
2438 {
2439         if (decalskinframe)
2440                 R_SkinFrame_MarkUsed(decalskinframe);
2441         CL_Particles_LoadEffectInfo(NULL);
2442 }
2443
2444 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2445 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2446
2447 void R_Particles_Init (void)
2448 {
2449         int i;
2450         for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2451         {
2452                 particle_elements[i*6+0] = i*4+0;
2453                 particle_elements[i*6+1] = i*4+1;
2454                 particle_elements[i*6+2] = i*4+2;
2455                 particle_elements[i*6+3] = i*4+0;
2456                 particle_elements[i*6+4] = i*4+2;
2457                 particle_elements[i*6+5] = i*4+3;
2458         }
2459
2460         Cvar_RegisterVariable(&r_drawparticles);
2461         Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2462         Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2463         Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2464         Cvar_RegisterVariable(&r_drawdecals);
2465         Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2466         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2467 }
2468
2469 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2470 {
2471         int surfacelistindex;
2472         const decal_t *d;
2473         float *v3f, *t2f, *c4f;
2474         particletexture_t *tex;
2475         vec_t right[3], up[3], size, ca;
2476         float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2477
2478         RSurf_ActiveWorldEntity();
2479
2480         r_refdef.stats[r_stat_drawndecals] += numsurfaces;
2481 //      R_Mesh_ResetTextureState();
2482         GL_DepthMask(false);
2483         GL_DepthRange(0, 1);
2484         GL_PolygonOffset(0, 0);
2485         GL_DepthTest(true);
2486         GL_CullFace(GL_NONE);
2487
2488         // generate all the vertices at once
2489         for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2490         {
2491                 d = cl.decals + surfacelist[surfacelistindex];
2492
2493                 // calculate color
2494                 c4f = particle_color4f + 16*surfacelistindex;
2495                 ca = d->alpha * alphascale;
2496                 // ensure alpha multiplier saturates properly
2497                 if (ca > 1.0f / 256.0f)
2498                         ca = 1.0f / 256.0f;     
2499                 if (r_refdef.fogenabled)
2500                         ca *= RSurf_FogVertex(d->org);
2501                 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2502                 Vector4Copy(c4f, c4f + 4);
2503                 Vector4Copy(c4f, c4f + 8);
2504                 Vector4Copy(c4f, c4f + 12);
2505
2506                 // calculate vertex positions
2507                 size = d->size * cl_particles_size.value;
2508                 VectorVectors(d->normal, right, up);
2509                 VectorScale(right, size, right);
2510                 VectorScale(up, size, up);
2511                 v3f = particle_vertex3f + 12*surfacelistindex;
2512                 v3f[ 0] = d->org[0] - right[0] - up[0];
2513                 v3f[ 1] = d->org[1] - right[1] - up[1];
2514                 v3f[ 2] = d->org[2] - right[2] - up[2];
2515                 v3f[ 3] = d->org[0] - right[0] + up[0];
2516                 v3f[ 4] = d->org[1] - right[1] + up[1];
2517                 v3f[ 5] = d->org[2] - right[2] + up[2];
2518                 v3f[ 6] = d->org[0] + right[0] + up[0];
2519                 v3f[ 7] = d->org[1] + right[1] + up[1];
2520                 v3f[ 8] = d->org[2] + right[2] + up[2];
2521                 v3f[ 9] = d->org[0] + right[0] - up[0];
2522                 v3f[10] = d->org[1] + right[1] - up[1];
2523                 v3f[11] = d->org[2] + right[2] - up[2];
2524
2525                 // calculate texcoords
2526                 tex = &particletexture[d->texnum];
2527                 t2f = particle_texcoord2f + 8*surfacelistindex;
2528                 t2f[0] = tex->s1;t2f[1] = tex->t2;
2529                 t2f[2] = tex->s1;t2f[3] = tex->t1;
2530                 t2f[4] = tex->s2;t2f[5] = tex->t1;
2531                 t2f[6] = tex->s2;t2f[7] = tex->t2;
2532         }
2533
2534         // now render the decals all at once
2535         // (this assumes they all use one particle font texture!)
2536         GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2537         R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2538         R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2539         R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2540 }
2541
2542 void R_DrawDecals (void)
2543 {
2544         int i;
2545         int drawdecals = r_drawdecals.integer;
2546         decal_t *decal;
2547         float frametime;
2548         float decalfade;
2549         float drawdist2;
2550         int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2551
2552         frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2553         cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2554
2555         // LordHavoc: early out conditions
2556         if (!cl.num_decals)
2557                 return;
2558
2559         decalfade = frametime * 256 / cl_decals_fadetime.value;
2560         drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2561         drawdist2 = drawdist2*drawdist2;
2562
2563         for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2564         {
2565                 if (!decal->typeindex)
2566                         continue;
2567
2568                 if (killsequence - decal->decalsequence > 0)
2569                         goto killdecal;
2570
2571                 if (cl.time > decal->time2 + cl_decals_time.value)
2572                 {
2573                         decal->alpha -= decalfade;
2574                         if (decal->alpha <= 0)
2575                                 goto killdecal;
2576                 }
2577
2578                 if (decal->owner)
2579                 {
2580                         if (cl.entities[decal->owner].render.model == decal->ownermodel)
2581                         {
2582                                 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2583                                 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2584                         }
2585                         else
2586                                 goto killdecal;
2587                 }
2588
2589                 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2590                         continue;
2591
2592                 if (!drawdecals)
2593                         continue;
2594
2595                 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))
2596                         R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2597                 continue;
2598 killdecal:
2599                 decal->typeindex = 0;
2600                 if (cl.free_decal > i)
2601                         cl.free_decal = i;
2602         }
2603
2604         // reduce cl.num_decals if possible
2605         while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2606                 cl.num_decals--;
2607
2608         if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2609         {
2610                 decal_t *olddecals = cl.decals;
2611                 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2612                 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2613                 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2614                 Mem_Free(olddecals);
2615         }
2616
2617         r_refdef.stats[r_stat_totaldecals] = cl.num_decals;
2618 }
2619
2620 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2621 {
2622         vec3_t vecorg, vecvel, baseright, baseup;
2623         int surfacelistindex;
2624         int batchstart, batchcount;
2625         const particle_t *p;
2626         pblend_t blendmode;
2627         rtexture_t *texture;
2628         float *v3f, *t2f, *c4f;
2629         particletexture_t *tex;
2630         float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2631 //      float ambient[3], diffuse[3], diffusenormal[3];
2632         float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2633         vec4_t colormultiplier;
2634         float minparticledist_start, minparticledist_end;
2635         qboolean dofade;
2636
2637         RSurf_ActiveWorldEntity();
2638
2639         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));
2640
2641         r_refdef.stats[r_stat_particles] += numsurfaces;
2642 //      R_Mesh_ResetTextureState();
2643         GL_DepthMask(false);
2644         GL_DepthRange(0, 1);
2645         GL_PolygonOffset(0, 0);
2646         GL_DepthTest(true);
2647         GL_CullFace(GL_NONE);
2648
2649         spintime = r_refdef.scene.time;
2650
2651         minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2652         minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2653         dofade = (minparticledist_start < minparticledist_end);
2654
2655         // first generate all the vertices at once
2656         for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2657         {
2658                 p = cl.particles + surfacelist[surfacelistindex];
2659
2660                 blendmode = (pblend_t)p->blendmode;
2661                 palpha = p->alpha;
2662                 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2663                         palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward)  - minparticledist_start) / (minparticledist_end - minparticledist_start));
2664                 alpha = palpha * colormultiplier[3];
2665                 // ensure alpha multiplier saturates properly
2666                 if (alpha > 1.0f)
2667                         alpha = 1.0f;
2668
2669                 switch (blendmode)
2670                 {
2671                 case PBLEND_INVALID:
2672                 case PBLEND_INVMOD:
2673                         // additive and modulate can just fade out in fog (this is correct)
2674                         if (r_refdef.fogenabled)
2675                                 alpha *= RSurf_FogVertex(p->org);
2676                         // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2677                         alpha *= 1.0f / 256.0f;
2678                         c4f[0] = p->color[0] * alpha;
2679                         c4f[1] = p->color[1] * alpha;
2680                         c4f[2] = p->color[2] * alpha;
2681                         c4f[3] = 0;
2682                         break;
2683                 case PBLEND_ADD:
2684                         // additive and modulate can just fade out in fog (this is correct)
2685                         if (r_refdef.fogenabled)
2686                                 alpha *= RSurf_FogVertex(p->org);
2687                         // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2688                         c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2689                         c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2690                         c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2691                         c4f[3] = 0;
2692                         break;
2693                 case PBLEND_ALPHA:
2694                         c4f[0] = p->color[0] * colormultiplier[0];
2695                         c4f[1] = p->color[1] * colormultiplier[1];
2696                         c4f[2] = p->color[2] * colormultiplier[2];
2697                         c4f[3] = alpha;
2698                         // note: lighting is not cheap!
2699                         if (particletype[p->typeindex].lighting)
2700                         {
2701                                 vecorg[0] = p->org[0];
2702                                 vecorg[1] = p->org[1];
2703                                 vecorg[2] = p->org[2];
2704                                 R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2705                         }
2706                         // mix in the fog color
2707                         if (r_refdef.fogenabled)
2708                         {
2709                                 fog = RSurf_FogVertex(p->org);
2710                                 ifog = 1 - fog;
2711                                 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2712                                 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2713                                 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2714                         }
2715                         // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2716                         VectorScale(c4f, alpha, c4f);
2717                         break;
2718                 }
2719                 // copy the color into the other three vertices
2720                 Vector4Copy(c4f, c4f + 4);
2721                 Vector4Copy(c4f, c4f + 8);
2722                 Vector4Copy(c4f, c4f + 12);
2723
2724                 size = p->size * cl_particles_size.value;
2725                 tex = &particletexture[p->texnum];
2726                 switch(p->orientation)
2727                 {
2728 //              case PARTICLE_INVALID:
2729                 case PARTICLE_BILLBOARD:
2730                         if (p->angle + p->spin)
2731                         {
2732                                 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2733                                 spinsin = sin(spinrad) * size;
2734                                 spincos = cos(spinrad) * size;
2735                                 spinm1 = -p->stretch * spincos;
2736                                 spinm2 = -spinsin;
2737                                 spinm3 = spinsin;
2738                                 spinm4 = -p->stretch * spincos;
2739                                 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2740                                 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2741                         }
2742                         else
2743                         {
2744                                 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2745                                 VectorScale(r_refdef.view.up, size, up);
2746                         }
2747
2748                         v3f[ 0] = p->org[0] - right[0] - up[0];
2749                         v3f[ 1] = p->org[1] - right[1] - up[1];
2750                         v3f[ 2] = p->org[2] - right[2] - up[2];
2751                         v3f[ 3] = p->org[0] - right[0] + up[0];
2752                         v3f[ 4] = p->org[1] - right[1] + up[1];
2753                         v3f[ 5] = p->org[2] - right[2] + up[2];
2754                         v3f[ 6] = p->org[0] + right[0] + up[0];
2755                         v3f[ 7] = p->org[1] + right[1] + up[1];
2756                         v3f[ 8] = p->org[2] + right[2] + up[2];
2757                         v3f[ 9] = p->org[0] + right[0] - up[0];
2758                         v3f[10] = p->org[1] + right[1] - up[1];
2759                         v3f[11] = p->org[2] + right[2] - up[2];
2760                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2761                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2762                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2763                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2764                         break;
2765                 case PARTICLE_ORIENTED_DOUBLESIDED:
2766                         vecvel[0] = p->vel[0];
2767                         vecvel[1] = p->vel[1];
2768                         vecvel[2] = p->vel[2];
2769                         VectorVectors(vecvel, baseright, baseup);
2770                         if (p->angle + p->spin)
2771                         {
2772                                 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2773                                 spinsin = sin(spinrad) * size;
2774                                 spincos = cos(spinrad) * size;
2775                                 spinm1 = p->stretch * spincos;
2776                                 spinm2 = -spinsin;
2777                                 spinm3 = spinsin;
2778                                 spinm4 = p->stretch * spincos;
2779                                 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2780                                 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2781                         }
2782                         else
2783                         {
2784                                 VectorScale(baseright, size * p->stretch, right);
2785                                 VectorScale(baseup, size, up);
2786                         }
2787                         v3f[ 0] = p->org[0] - right[0] - up[0];
2788                         v3f[ 1] = p->org[1] - right[1] - up[1];
2789                         v3f[ 2] = p->org[2] - right[2] - up[2];
2790                         v3f[ 3] = p->org[0] - right[0] + up[0];
2791                         v3f[ 4] = p->org[1] - right[1] + up[1];
2792                         v3f[ 5] = p->org[2] - right[2] + up[2];
2793                         v3f[ 6] = p->org[0] + right[0] + up[0];
2794                         v3f[ 7] = p->org[1] + right[1] + up[1];
2795                         v3f[ 8] = p->org[2] + right[2] + up[2];
2796                         v3f[ 9] = p->org[0] + right[0] - up[0];
2797                         v3f[10] = p->org[1] + right[1] - up[1];
2798                         v3f[11] = p->org[2] + right[2] - up[2];
2799                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2800                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2801                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2802                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2803                         break;
2804                 case PARTICLE_SPARK:
2805                         len = VectorLength(p->vel);
2806                         VectorNormalize2(p->vel, up);
2807                         lenfactor = p->stretch * 0.04 * len;
2808                         if(lenfactor < size * 0.5)
2809                                 lenfactor = size * 0.5;
2810                         VectorMA(p->org, -lenfactor, up, v);
2811                         VectorMA(p->org,  lenfactor, up, up2);
2812                         R_CalcBeam_Vertex3f(v3f, v, up2, size);
2813                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2814                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2815                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2816                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2817                         break;
2818                 case PARTICLE_VBEAM:
2819                         R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2820                         VectorSubtract(p->vel, p->org, up);
2821                         VectorNormalize(up);
2822                         v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2823                         v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2824                         t2f[0] = tex->s2;t2f[1] = v[0];
2825                         t2f[2] = tex->s1;t2f[3] = v[0];
2826                         t2f[4] = tex->s1;t2f[5] = v[1];
2827                         t2f[6] = tex->s2;t2f[7] = v[1];
2828                         break;
2829                 case PARTICLE_HBEAM:
2830                         R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2831                         VectorSubtract(p->vel, p->org, up);
2832                         VectorNormalize(up);
2833                         v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2834                         v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2835                         t2f[0] = v[0];t2f[1] = tex->t1;
2836                         t2f[2] = v[0];t2f[3] = tex->t2;
2837                         t2f[4] = v[1];t2f[5] = tex->t2;
2838                         t2f[6] = v[1];t2f[7] = tex->t1;
2839                         break;
2840                 }
2841         }
2842
2843         // now render batches of particles based on blendmode and texture
2844         blendmode = PBLEND_INVALID;
2845         texture = NULL;
2846         batchstart = 0;
2847         batchcount = 0;
2848         R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2849         for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2850         {
2851                 p = cl.particles + surfacelist[surfacelistindex];
2852
2853                 if (texture != particletexture[p->texnum].texture)
2854                 {
2855                         texture = particletexture[p->texnum].texture;
2856                         R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2857                 }
2858
2859                 if (p->blendmode == PBLEND_INVMOD)
2860                 {
2861                         // inverse modulate blend - group these
2862                         GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2863                         // iterate until we find a change in settings
2864                         batchstart = surfacelistindex++;
2865                         for (;surfacelistindex < numsurfaces;surfacelistindex++)
2866                         {
2867                                 p = cl.particles + surfacelist[surfacelistindex];
2868                                 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2869                                         break;
2870                         }
2871                 }
2872                 else
2873                 {
2874                         // additive or alpha blend - group these
2875                         // (we can group these because we premultiplied the texture alpha)
2876                         GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2877                         // iterate until we find a change in settings
2878                         batchstart = surfacelistindex++;
2879                         for (;surfacelistindex < numsurfaces;surfacelistindex++)
2880                         {
2881                                 p = cl.particles + surfacelist[surfacelistindex];
2882                                 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2883                                         break;
2884                         }
2885                 }
2886
2887                 batchcount = surfacelistindex - batchstart;
2888                 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2889         }
2890 }
2891
2892 void R_DrawParticles (void)
2893 {
2894         int i, a;
2895         int drawparticles = r_drawparticles.integer;
2896         float minparticledist_start;
2897         particle_t *p;
2898         float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2899         float drawdist2;
2900         int hitent;
2901         trace_t trace;
2902         qboolean update;
2903
2904         frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2905         cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2906
2907         // LordHavoc: early out conditions
2908         if (!cl.num_particles)
2909                 return;
2910
2911         minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2912         gravity = frametime * cl.movevars_gravity;
2913         update = frametime > 0;
2914         drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2915         drawdist2 = drawdist2*drawdist2;
2916
2917         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2918         {
2919                 if (!p->typeindex)
2920                 {
2921                         if (cl.free_particle > i)
2922                                 cl.free_particle = i;
2923                         continue;
2924                 }
2925
2926                 if (update)
2927                 {
2928                         if (p->delayedspawn > cl.time)
2929                                 continue;
2930
2931                         p->size += p->sizeincrease * frametime;
2932                         p->alpha -= p->alphafade * frametime;
2933
2934                         if (p->alpha <= 0 || p->die <= cl.time)
2935                                 goto killparticle;
2936
2937                         if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2938                         {
2939                                 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2940                                 {
2941                                         if (p->typeindex == pt_blood)
2942                                                 p->size += frametime * 8;
2943                                         else
2944                                                 p->vel[2] -= p->gravity * gravity;
2945                                         f = 1.0f - min(p->liquidfriction * frametime, 1);
2946                                         VectorScale(p->vel, f, p->vel);
2947                                 }
2948                                 else
2949                                 {
2950                                         p->vel[2] -= p->gravity * gravity;
2951                                         if (p->airfriction)
2952                                         {
2953                                                 f = 1.0f - min(p->airfriction * frametime, 1);
2954                                                 VectorScale(p->vel, f, p->vel);
2955                                         }
2956                                 }
2957
2958                                 VectorCopy(p->org, oldorg);
2959                                 VectorMA(p->org, frametime, p->vel, p->org);
2960 //                              if (p->bounce && cl.time >= p->delayedcollisions)
2961                                 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2962                                 {
2963                                         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);
2964                                         // if the trace started in or hit something of SUPERCONTENTS_NODROP
2965                                         // or if the trace hit something flagged as NOIMPACT
2966                                         // then remove the particle
2967                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2968                                                 goto killparticle;
2969                                         VectorCopy(trace.endpos, p->org);
2970                                         // react if the particle hit something
2971                                         if (trace.fraction < 1)
2972                                         {
2973                                                 VectorCopy(trace.endpos, p->org);
2974
2975                                                 if (p->staintexnum >= 0)
2976                                                 {
2977                                                         // blood - splash on solid
2978                                                         if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2979                                                         {
2980                                                                 R_Stain(p->org, 16,
2981                                                                         p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2982                                                                         p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2983                                                                 if (cl_decals.integer)
2984                                                                 {
2985                                                                         // create a decal for the blood splat
2986                                                                         a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2987                                                                         if (cl_decals_newsystem_bloodsmears.integer)
2988                                                                         {
2989                                                                                 VectorCopy(p->vel, decaldir);
2990                                                                                 VectorNormalize(decaldir);
2991                                                                         }
2992                                                                         else
2993                                                                                 VectorCopy(trace.plane.normal, decaldir);
2994                                                                         CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2995                                                                 }
2996                                                         }
2997                                                 }
2998
2999                                                 if (p->typeindex == pt_blood)
3000                                                 {
3001                                                         // blood - splash on solid
3002                                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
3003                                                                 goto killparticle;
3004                                                         if(p->staintexnum == -1) // staintex < -1 means no stains at all
3005                                                         {
3006                                                                 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)));
3007                                                                 if (cl_decals.integer)
3008                                                                 {
3009                                                                         // create a decal for the blood splat
3010                                                                         if (cl_decals_newsystem_bloodsmears.integer)
3011                                                                         {
3012                                                                                 VectorCopy(p->vel, decaldir);
3013                                                                                 VectorNormalize(decaldir);
3014                                                                         }
3015                                                                         else
3016                                                                                 VectorCopy(trace.plane.normal, decaldir);
3017                                                                         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);
3018                                                                 }
3019                                                         }
3020                                                         goto killparticle;
3021                                                 }
3022                                                 else if (p->bounce < 0)
3023                                                 {
3024                                                         // bounce -1 means remove on impact
3025                                                         goto killparticle;
3026                                                 }
3027                                                 else
3028                                                 {
3029                                                         // anything else - bounce off solid
3030                                                         dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
3031                                                         VectorMA(p->vel, dist, trace.plane.normal, p->vel);
3032                                                 }
3033                                         }
3034                                 }
3035
3036                                 if (VectorLength2(p->vel) < 0.03)
3037                                 {
3038                                         if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
3039                                                 goto killparticle;
3040                                         VectorClear(p->vel);
3041                                 }
3042                         }
3043
3044                         if (p->typeindex != pt_static)
3045                         {
3046                                 switch (p->typeindex)
3047                                 {
3048                                 case pt_entityparticle:
3049                                         // particle that removes itself after one rendered frame
3050                                         if (p->time2)
3051                                                 goto killparticle;
3052                                         else
3053                                                 p->time2 = 1;
3054                                         break;
3055                                 case pt_blood:
3056                                         a = CL_PointSuperContents(p->org);
3057                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
3058                                                 goto killparticle;
3059                                         break;
3060                                 case pt_bubble:
3061                                         a = CL_PointSuperContents(p->org);
3062                                         if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
3063                                                 goto killparticle;
3064                                         break;
3065                                 case pt_rain:
3066                                         a = CL_PointSuperContents(p->org);
3067                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3068                                                 goto killparticle;
3069                                         break;
3070                                 case pt_snow:
3071                                         if (cl.time > p->time2)
3072                                         {
3073                                                 // snow flutter
3074                                                 p->time2 = cl.time + (rand() & 3) * 0.1;
3075                                                 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3076                                                 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3077                                         }
3078                                         a = CL_PointSuperContents(p->org);
3079                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3080                                                 goto killparticle;
3081                                         break;
3082                                 default:
3083                                         break;
3084                                 }
3085                         }
3086                 }
3087                 else if (p->delayedspawn > cl.time)
3088                         continue;
3089                 if (!drawparticles)
3090                         continue;
3091                 // don't render particles too close to the view (they chew fillrate)
3092                 // also don't render particles behind the view (useless)
3093                 // further checks to cull to the frustum would be too slow here
3094                 switch(p->typeindex)
3095                 {
3096                 case pt_beam:
3097                         // beams have no culling
3098                         R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3099                         break;
3100                 default:
3101                         if(cl_particles_visculling.integer)
3102                                 if (!r_refdef.viewcache.world_novis)
3103                                         if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3104                                         {
3105                                                 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3106                                                 if(leaf)
3107                                                         if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3108                                                                 continue;
3109                                         }
3110                         // anything else just has to be in front of the viewer and visible at this distance
3111                         if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3112                                 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3113                         break;
3114                 }
3115
3116                 continue;
3117 killparticle:
3118                 p->typeindex = 0;
3119                 if (cl.free_particle > i)
3120                         cl.free_particle = i;
3121         }
3122
3123         // reduce cl.num_particles if possible
3124         while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3125                 cl.num_particles--;
3126
3127         if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3128         {
3129                 particle_t *oldparticles = cl.particles;
3130                 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3131                 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3132                 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3133                 Mem_Free(oldparticles);
3134         }
3135 }