]> git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_particles.c
cl_particles_forcetraileffects: emulate old trail behaviour (Xonotic 0.7 compatibility).
[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                                 // trail effects may only ever be drawn as trail
1509                                 if (!drawastrail && definedastrail)
1510                                         continue;
1511
1512                                 // spawn a dlight if requested
1513                                 if (info->lightradiusstart > 0 && spawndlight)
1514                                 {
1515                                         matrix4x4_t tempmatrix;
1516                                         if (drawastrail)
1517                                                 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1518                                         else
1519                                                 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1520                                         if (info->lighttime > 0 && info->lightradiusfade > 0)
1521                                         {
1522                                                 // light flash (explosion, etc)
1523                                                 // called when effect starts
1524                                                 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);
1525                                         }
1526                                         else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1527                                         {
1528                                                 // glowing entity
1529                                                 // called by CL_LinkNetworkEntity
1530                                                 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1531                                                 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1532                                                 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1533                                                 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1534                                                 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);
1535                                                 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1536                                         }
1537                                 }
1538
1539                                 if (!spawnparticles)
1540                                         continue;
1541
1542                                 // spawn particles
1543                                 tex = info->tex[0];
1544                                 if (info->tex[1] > info->tex[0])
1545                                 {
1546                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1547                                         tex = min(tex, info->tex[1] - 1);
1548                                 }
1549                                 if(info->staintex[0] < 0)
1550                                         staintex = info->staintex[0];
1551                                 else
1552                                 {
1553                                         staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1554                                         staintex = min(staintex, info->staintex[1] - 1);
1555                                 }
1556                                 if (info->particletype == pt_decal)
1557                                 {
1558                                         VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1559                                         AnglesFromVectors(angles, velocity, NULL, false);
1560                                         AngleVectors(angles, forward, right, up);
1561                                         VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1562
1563                                         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]);
1564                                 }
1565                                 else if (info->orientation == PARTICLE_HBEAM)
1566                                 {
1567                                         AnglesFromVectors(angles, traildir, NULL, false);
1568                                         AngleVectors(angles, forward, right, up);
1569                                         VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1570
1571                                         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);
1572                                 }
1573                                 else
1574                                 {
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                                         VectorCopy(originmins, trailpos);
1588                                         if (drawastrail)
1589                                         {
1590                                                 float cnt = info->countabsolute;
1591                                                 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1592                                                 if (info->trailspacing > 0)
1593                                                         cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1594                                                 cnt *= fade;
1595                                                 info->particleaccumulator += cnt;
1596                                                 trailstep = traillen / cnt;
1597                                                 immediatebloodstain = false;
1598
1599                                                 AnglesFromVectors(angles, traildir, NULL, false);
1600                                         }
1601                                         else
1602                                         {
1603                                                 float cnt = info->countabsolute;
1604                                                 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1605                                                 cnt *= fade;
1606                                                 info->particleaccumulator += cnt;
1607                                                 trailstep = 0;
1608                                                 immediatebloodstain =
1609                                                         ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1610                                                         ||
1611                                                         ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1612
1613                                                 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1614                                                 AnglesFromVectors(angles, velocity, NULL, false);
1615                                         }
1616                                         AngleVectors(angles, forward, right, up);
1617                                         VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1618                                         VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1619                                         info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1620                                         for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1621                                         {
1622                                                 if (info->tex[1] > info->tex[0])
1623                                                 {
1624                                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1625                                                         tex = min(tex, info->tex[1] - 1);
1626                                                 }
1627                                                 if (!trailstep)
1628                                                 {
1629                                                         trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1630                                                         trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1631                                                         trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1632                                                 }
1633                                                 if(tintmins)
1634                                                 {
1635                                                         tintlerp = lhrandom(0, 1);
1636                                                         Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1637                                                 }
1638                                                 VectorRandom(rvec);
1639                                                 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);
1640                                                 if (immediatebloodstain && part)
1641                                                 {
1642                                                         immediatebloodstain = false;
1643                                                         CL_ImmediateBloodStain(part);
1644                                                 }
1645                                                 if (trailstep)
1646                                                         VectorMA(trailpos, trailstep, traildir, trailpos);
1647                                         }
1648                                 }
1649                         }
1650                 }
1651         }
1652         if (!found)
1653                 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1654 }
1655
1656 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)
1657 {
1658         CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1659 }
1660
1661 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)
1662 {
1663         CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1664 }
1665
1666 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)
1667 {
1668         CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1669 }
1670
1671 /*
1672 ===============
1673 CL_EntityParticles
1674 ===============
1675 */
1676 void CL_EntityParticles (const entity_t *ent)
1677 {
1678         int i, j;
1679         vec_t pitch, yaw, dist = 64, beamlength = 16;
1680         vec3_t org, v;
1681         static vec3_t avelocities[NUMVERTEXNORMALS];
1682         if (!cl_particles.integer) return;
1683         if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1684
1685         Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1686
1687         if (!avelocities[0][0])
1688                 for (i = 0;i < NUMVERTEXNORMALS;i++)
1689                         for (j = 0;j < 3;j++)
1690                                 avelocities[i][j] = lhrandom(0, 2.55);
1691
1692         for (i = 0;i < NUMVERTEXNORMALS;i++)
1693         {
1694                 yaw = cl.time * avelocities[i][0];
1695                 pitch = cl.time * avelocities[i][1];
1696                 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1697                 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1698                 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1699                 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);
1700         }
1701 }
1702
1703
1704 void CL_ReadPointFile_f (void)
1705 {
1706         double org[3], leakorg[3];
1707         vec3_t vecorg;
1708         int r, c, s;
1709         char *pointfile = NULL, *pointfilepos, *t, tchar;
1710         char name[MAX_QPATH];
1711
1712         if (!cl.worldmodel)
1713                 return;
1714
1715         dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1716         pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1717         if (!pointfile)
1718         {
1719                 Con_Printf("Could not open %s\n", name);
1720                 return;
1721         }
1722
1723         Con_Printf("Reading %s...\n", name);
1724         VectorClear(leakorg);
1725         c = 0;
1726         s = 0;
1727         pointfilepos = pointfile;
1728         while (*pointfilepos)
1729         {
1730                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1731                         pointfilepos++;
1732                 if (!*pointfilepos)
1733                         break;
1734                 t = pointfilepos;
1735                 while (*t && *t != '\n' && *t != '\r')
1736                         t++;
1737                 tchar = *t;
1738                 *t = 0;
1739 #if _MSC_VER >= 1400
1740 #define sscanf sscanf_s
1741 #endif
1742                 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1743                 VectorCopy(org, vecorg);
1744                 *t = tchar;
1745                 pointfilepos = t;
1746                 if (r != 3)
1747                         break;
1748                 if (c == 0)
1749                         VectorCopy(org, leakorg);
1750                 c++;
1751
1752                 if (cl.num_particles < cl.max_particles - 3)
1753                 {
1754                         s++;
1755                         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);
1756                 }
1757         }
1758         Mem_Free(pointfile);
1759         VectorCopy(leakorg, vecorg);
1760         Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1761
1762         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);
1763         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);
1764         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);
1765 }
1766
1767 /*
1768 ===============
1769 CL_ParseParticleEffect
1770
1771 Parse an effect out of the server message
1772 ===============
1773 */
1774 void CL_ParseParticleEffect (void)
1775 {
1776         vec3_t org, dir;
1777         int i, count, msgcount, color;
1778
1779         MSG_ReadVector(&cl_message, org, cls.protocol);
1780         for (i=0 ; i<3 ; i++)
1781                 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1782         msgcount = MSG_ReadByte(&cl_message);
1783         color = MSG_ReadByte(&cl_message);
1784
1785         if (msgcount == 255)
1786                 count = 1024;
1787         else
1788                 count = msgcount;
1789
1790         CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1791 }
1792
1793 /*
1794 ===============
1795 CL_ParticleExplosion
1796
1797 ===============
1798 */
1799 void CL_ParticleExplosion (const vec3_t org)
1800 {
1801         int i;
1802         trace_t trace;
1803         //vec3_t v;
1804         //vec3_t v2;
1805         R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1806         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1807
1808         if (cl_particles_quake.integer)
1809         {
1810                 for (i = 0;i < 1024;i++)
1811                 {
1812                         int r, color;
1813                         r = rand()&3;
1814                         if (i & 1)
1815                         {
1816                                 color = particlepalette[ramp1[r]];
1817                                 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);
1818                         }
1819                         else
1820                         {
1821                                 color = particlepalette[ramp2[r]];
1822                                 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);
1823                         }
1824                 }
1825         }
1826         else
1827         {
1828                 i = CL_PointSuperContents(org);
1829                 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1830                 {
1831                         if (cl_particles.integer && cl_particles_bubbles.integer)
1832                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1833                                         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);
1834                 }
1835                 else
1836                 {
1837                         if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1838                         {
1839                                 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1840                                 {
1841                                         int k = 0;
1842                                         vec3_t v, v2;
1843                                         do
1844                                         {
1845                                                 VectorRandom(v2);
1846                                                 VectorMA(org, 128, v2, v);
1847                                                 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1848                                         }
1849                                         while (k < 16 && trace.fraction < 0.1f);
1850                                         VectorSubtract(trace.endpos, org, v2);
1851                                         VectorScale(v2, 2.0f, v2);
1852                                         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);
1853                                 }
1854                         }
1855                 }
1856         }
1857
1858         if (cl_particles_explosions_shell.integer)
1859                 R_NewExplosion(org);
1860 }
1861
1862 /*
1863 ===============
1864 CL_ParticleExplosion2
1865
1866 ===============
1867 */
1868 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1869 {
1870         int i, k;
1871         if (!cl_particles.integer) return;
1872
1873         for (i = 0;i < 512 * cl_particles_quality.value;i++)
1874         {
1875                 k = particlepalette[colorStart + (i % colorLength)];
1876                 if (cl_particles_quake.integer)
1877                         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);
1878                 else
1879                         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);
1880         }
1881 }
1882
1883 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1884 {
1885         vec3_t center;
1886         VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1887         if (cl_particles_sparks.integer)
1888         {
1889                 sparkcount *= cl_particles_quality.value;
1890                 while(sparkcount-- > 0)
1891                         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);
1892         }
1893 }
1894
1895 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1896 {
1897         vec3_t center;
1898         VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1899         if (cl_particles_smoke.integer)
1900         {
1901                 smokecount *= cl_particles_quality.value;
1902                 while(smokecount-- > 0)
1903                         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);
1904         }
1905 }
1906
1907 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)
1908 {
1909         vec3_t center;
1910         int k;
1911         if (!cl_particles.integer) return;
1912         VectorMAM(0.5f, mins, 0.5f, maxs, center);
1913
1914         count = (int)(count * cl_particles_quality.value);
1915         while (count--)
1916         {
1917                 k = particlepalette[colorbase + (rand()&3)];
1918                 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);
1919         }
1920 }
1921
1922 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1923 {
1924         int k;
1925         float minz, maxz, lifetime = 30;
1926         vec3_t org;
1927         if (!cl_particles.integer) return;
1928         if (dir[2] < 0) // falling
1929         {
1930                 minz = maxs[2] + dir[2] * 0.1;
1931                 maxz = maxs[2];
1932                 if (cl.worldmodel)
1933                         lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1934         }
1935         else // rising??
1936         {
1937                 minz = mins[2];
1938                 maxz = maxs[2] + dir[2] * 0.1;
1939                 if (cl.worldmodel)
1940                         lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1941         }
1942
1943         count = (int)(count * cl_particles_quality.value);
1944
1945         switch(type)
1946         {
1947         case 0:
1948                 if (!cl_particles_rain.integer) break;
1949                 count *= 4; // ick, this should be in the mod or maps?
1950
1951                 while(count--)
1952                 {
1953                         k = particlepalette[colorbase + (rand()&3)];
1954                         VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1955                         if (gamemode == GAME_GOODVSBAD2)
1956                                 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);
1957                         else
1958                                 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);
1959                 }
1960                 break;
1961         case 1:
1962                 if (!cl_particles_snow.integer) break;
1963                 while(count--)
1964                 {
1965                         k = particlepalette[colorbase + (rand()&3)];
1966                         VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1967                         if (gamemode == GAME_GOODVSBAD2)
1968                                 CL_NewParticle(org, pt_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);
1969                         else
1970                                 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);
1971                 }
1972                 break;
1973         default:
1974                 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1975         }
1976 }
1977
1978 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1979 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1980 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1981 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1982 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1983 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1984
1985 #define PARTICLETEXTURESIZE 64
1986 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1987
1988 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1989 {
1990         float dz, f, dot;
1991         vec3_t normal;
1992         dz = 1 - (dx*dx+dy*dy);
1993         if (dz > 0) // it does hit the sphere
1994         {
1995                 f = 0;
1996                 // back side
1997                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1998                 VectorNormalize(normal);
1999                 dot = DotProduct(normal, light);
2000                 if (dot > 0.5) // interior reflection
2001                         f += ((dot *  2) - 1);
2002                 else if (dot < -0.5) // exterior reflection
2003                         f += ((dot * -2) - 1);
2004                 // front side
2005                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
2006                 VectorNormalize(normal);
2007                 dot = DotProduct(normal, light);
2008                 if (dot > 0.5) // interior reflection
2009                         f += ((dot *  2) - 1);
2010                 else if (dot < -0.5) // exterior reflection
2011                         f += ((dot * -2) - 1);
2012                 f *= 128;
2013                 f += 16; // just to give it a haze so you can see the outline
2014                 f = bound(0, f, 255);
2015                 return (unsigned char) f;
2016         }
2017         else
2018                 return 0;
2019 }
2020
2021 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
2022 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
2023 {
2024         *basex = (texnum % particlefontcols) * particlefontcellwidth;
2025         *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
2026         *width = particlefontcellwidth;
2027         *height = particlefontcellheight;
2028 }
2029
2030 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
2031 {
2032         int basex, basey, w, h, y;
2033         CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2034         if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2035                 Sys_Error("invalid particle texture size for autogenerating");
2036         for (y = 0;y < PARTICLETEXTURESIZE;y++)
2037                 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2038 }
2039
2040 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2041 {
2042         int x, y;
2043         float cx, cy, dx, dy, f, iradius;
2044         unsigned char *d;
2045         cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2046         cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2047         iradius = 1.0f / radius;
2048         alpha *= (1.0f / 255.0f);
2049         for (y = 0;y < PARTICLETEXTURESIZE;y++)
2050         {
2051                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2052                 {
2053                         dx = (x - cx);
2054                         dy = (y - cy);
2055                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2056                         if (f > 0)
2057                         {
2058                                 if (f > 1)
2059                                         f = 1;
2060                                 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2061                                 d[0] += (int)(f * (blue  - d[0]));
2062                                 d[1] += (int)(f * (green - d[1]));
2063                                 d[2] += (int)(f * (red   - d[2]));
2064                         }
2065                 }
2066         }
2067 }
2068
2069 #if 0
2070 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2071 {
2072         int i;
2073         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2074         {
2075                 data[0] = bound(minb, data[0], maxb);
2076                 data[1] = bound(ming, data[1], maxg);
2077                 data[2] = bound(minr, data[2], maxr);
2078         }
2079 }
2080 #endif
2081
2082 static void particletextureinvert(unsigned char *data)
2083 {
2084         int i;
2085         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2086         {
2087                 data[0] = 255 - data[0];
2088                 data[1] = 255 - data[1];
2089                 data[2] = 255 - data[2];
2090         }
2091 }
2092
2093 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2094 static void R_InitBloodTextures (unsigned char *particletexturedata)
2095 {
2096         int i, j, k, m;
2097         size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2098         unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2099
2100         // blood particles
2101         for (i = 0;i < 8;i++)
2102         {
2103                 memset(data, 255, datasize);
2104                 for (k = 0;k < 24;k++)
2105                         particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2106                 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2107                 particletextureinvert(data);
2108                 setuptex(tex_bloodparticle[i], data, particletexturedata);
2109         }
2110
2111         // blood decals
2112         for (i = 0;i < 8;i++)
2113         {
2114                 memset(data, 255, datasize);
2115                 m = 8;
2116                 for (j = 1;j < 10;j++)
2117                         for (k = min(j, m - 1);k < m;k++)
2118                                 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2119                 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2120                 particletextureinvert(data);
2121                 setuptex(tex_blooddecal[i], data, particletexturedata);
2122         }
2123
2124         Mem_Free(data);
2125 }
2126
2127 //uncomment this to make engine save out particle font to a tga file when run
2128 //#define DUMPPARTICLEFONT
2129
2130 static void R_InitParticleTexture (void)
2131 {
2132         int x, y, d, i, k, m;
2133         int basex, basey, w, h;
2134         float dx, dy, f, s1, t1, s2, t2;
2135         vec3_t light;
2136         char *buf;
2137         fs_offset_t filesize;
2138         char texturename[MAX_QPATH];
2139         skinframe_t *sf;
2140
2141         // a note: decals need to modulate (multiply) the background color to
2142         // properly darken it (stain), and they need to be able to alpha fade,
2143         // this is a very difficult challenge because it means fading to white
2144         // (no change to background) rather than black (darkening everything
2145         // behind the whole decal polygon), and to accomplish this the texture is
2146         // inverted (dark red blood on white background becomes brilliant cyan
2147         // and white on black background) so we can alpha fade it to black, then
2148         // we invert it again during the blendfunc to make it work...
2149
2150 #ifndef DUMPPARTICLEFONT
2151         decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2152         if (decalskinframe)
2153         {
2154                 particlefonttexture = decalskinframe->base;
2155                 // TODO maybe allow custom grid size?
2156                 particlefontwidth = image_width;
2157                 particlefontheight = image_height;
2158                 particlefontcellwidth = image_width / 8;
2159                 particlefontcellheight = image_height / 8;
2160                 particlefontcols = 8;
2161                 particlefontrows = 8;
2162         }
2163         else
2164 #endif
2165         {
2166                 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2167                 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2168                 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2169                 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2170                 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2171
2172                 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2173                 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2174                 particlefontcols = 8;
2175                 particlefontrows = 8;
2176
2177                 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2178
2179                 // smoke
2180                 for (i = 0;i < 8;i++)
2181                 {
2182                         memset(data, 255, datasize);
2183                         do
2184                         {
2185                                 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2186                                 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2187                                 m = 0;
2188                                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2189                                 {
2190                                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2191                                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2192                                         {
2193                                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2194                                                 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2195                                                 if (d > 0)
2196                                                         d = (int)(d * (1-(dx*dx+dy*dy)));
2197                                                 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2198                                                 d = bound(0, d, 255);
2199                                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2200                                                 if (m < d)
2201                                                         m = d;
2202                                         }
2203                                 }
2204                         }
2205                         while (m < 224);
2206                         setuptex(tex_smoke[i], data, particletexturedata);
2207                 }
2208
2209                 // rain splash
2210                 memset(data, 255, datasize);
2211                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2212                 {
2213                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2214                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2215                         {
2216                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2217                                 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2218                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2219                         }
2220                 }
2221                 setuptex(tex_rainsplash, data, particletexturedata);
2222
2223                 // normal particle
2224                 memset(data, 255, datasize);
2225                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2226                 {
2227                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2228                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2229                         {
2230                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2231                                 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2232                                 d = bound(0, d, 255);
2233                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2234                         }
2235                 }
2236                 setuptex(tex_particle, data, particletexturedata);
2237
2238                 // rain
2239                 memset(data, 255, datasize);
2240                 light[0] = 1;light[1] = 1;light[2] = 1;
2241                 VectorNormalize(light);
2242                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2243                 {
2244                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2245                         // stretch upper half of bubble by +50% and shrink lower half by -50%
2246                         // (this gives an elongated teardrop shape)
2247                         if (dy > 0.5f)
2248                                 dy = (dy - 0.5f) * 2.0f;
2249                         else
2250                                 dy = (dy - 0.5f) / 1.5f;
2251                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2252                         {
2253                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2254                                 // shrink bubble width to half
2255                                 dx *= 2.0f;
2256                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2257                         }
2258                 }
2259                 setuptex(tex_raindrop, data, particletexturedata);
2260
2261                 // bubble
2262                 memset(data, 255, datasize);
2263                 light[0] = 1;light[1] = 1;light[2] = 1;
2264                 VectorNormalize(light);
2265                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2266                 {
2267                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2268                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2269                         {
2270                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2271                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2272                         }
2273                 }
2274                 setuptex(tex_bubble, data, particletexturedata);
2275
2276                 // Blood particles and blood decals
2277                 R_InitBloodTextures (particletexturedata);
2278
2279                 // bullet decals
2280                 for (i = 0;i < 8;i++)
2281                 {
2282                         memset(data, 255, datasize);
2283                         for (k = 0;k < 12;k++)
2284                                 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2285                         for (k = 0;k < 3;k++)
2286                                 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2287                         //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2288                         particletextureinvert(data);
2289                         setuptex(tex_bulletdecal[i], data, particletexturedata);
2290                 }
2291
2292 #ifdef DUMPPARTICLEFONT
2293                 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2294 #endif
2295
2296                 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2297                 particlefonttexture = decalskinframe->base;
2298
2299                 Mem_Free(particletexturedata);
2300                 Mem_Free(data);
2301                 Mem_Free(noise1);
2302                 Mem_Free(noise2);
2303         }
2304         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2305         {
2306                 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2307                 particletexture[i].texture = particlefonttexture;
2308                 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2309                 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2310                 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2311                 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2312         }
2313
2314 #ifndef DUMPPARTICLEFONT
2315         particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2316         if (!particletexture[tex_beam].texture)
2317 #endif
2318         {
2319                 unsigned char noise3[64][64], data2[64][16][4];
2320                 // nexbeam
2321                 fractalnoise(&noise3[0][0], 64, 4);
2322                 m = 0;
2323                 for (y = 0;y < 64;y++)
2324                 {
2325                         dy = (y - 0.5f*64) / (64*0.5f-1);
2326                         for (x = 0;x < 16;x++)
2327                         {
2328                                 dx = (x - 0.5f*16) / (16*0.5f-2);
2329                                 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2330                                 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2331                                 data2[y][x][3] = 255;
2332                         }
2333                 }
2334
2335 #ifdef DUMPPARTICLEFONT
2336                 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2337 #endif
2338                 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2339         }
2340         particletexture[tex_beam].s1 = 0;
2341         particletexture[tex_beam].t1 = 0;
2342         particletexture[tex_beam].s2 = 1;
2343         particletexture[tex_beam].t2 = 1;
2344
2345         // now load an texcoord/texture override file
2346         buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2347         if(buf)
2348         {
2349                 const char *bufptr;
2350                 bufptr = buf;
2351                 for(;;)
2352                 {
2353                         if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2354                                 break;
2355                         if(!strcmp(com_token, "\n"))
2356                                 continue; // empty line
2357                         i = atoi(com_token);
2358
2359                         texturename[0] = 0;
2360                         s1 = 0;
2361                         t1 = 0;
2362                         s2 = 1;
2363                         t2 = 1;
2364
2365                         if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2366                         {
2367                                 strlcpy(texturename, com_token, sizeof(texturename));
2368                                 s1 = atof(com_token);
2369                                 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2370                                 {
2371                                         texturename[0] = 0;
2372                                         t1 = atof(com_token);
2373                                         if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2374                                         {
2375                                                 s2 = atof(com_token);
2376                                                 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2377                                                 {
2378                                                         t2 = atof(com_token);
2379                                                         strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2380                                                         if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2381                                                                 strlcpy(texturename, com_token, sizeof(texturename));
2382                                                 }
2383                                         }
2384                                 }
2385                                 else
2386                                         s1 = 0;
2387                         }
2388                         if (!texturename[0])
2389                         {
2390                                 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2391                                 continue;
2392                         }
2393                         if (i < 0 || i >= MAX_PARTICLETEXTURES)
2394                         {
2395                                 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2396                                 continue;
2397                         }
2398                         sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2399                         if(!sf)
2400                         {
2401                                 // R_SkinFrame_LoadExternal already complained
2402                                 continue;
2403                         }
2404                         particletexture[i].texture = sf->base;
2405                         particletexture[i].s1 = s1;
2406                         particletexture[i].t1 = t1;
2407                         particletexture[i].s2 = s2;
2408                         particletexture[i].t2 = t2;
2409                 }
2410                 Mem_Free(buf);
2411         }
2412 }
2413
2414 static void r_part_start(void)
2415 {
2416         int i;
2417         // generate particlepalette for convenience from the main one
2418         for (i = 0;i < 256;i++)
2419                 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2420         particletexturepool = R_AllocTexturePool();
2421         R_InitParticleTexture ();
2422         CL_Particles_LoadEffectInfo(NULL);
2423 }
2424
2425 static void r_part_shutdown(void)
2426 {
2427         R_FreeTexturePool(&particletexturepool);
2428 }
2429
2430 static void r_part_newmap(void)
2431 {
2432         if (decalskinframe)
2433                 R_SkinFrame_MarkUsed(decalskinframe);
2434         CL_Particles_LoadEffectInfo(NULL);
2435 }
2436
2437 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2438 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2439
2440 void R_Particles_Init (void)
2441 {
2442         int i;
2443         for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2444         {
2445                 particle_elements[i*6+0] = i*4+0;
2446                 particle_elements[i*6+1] = i*4+1;
2447                 particle_elements[i*6+2] = i*4+2;
2448                 particle_elements[i*6+3] = i*4+0;
2449                 particle_elements[i*6+4] = i*4+2;
2450                 particle_elements[i*6+5] = i*4+3;
2451         }
2452
2453         Cvar_RegisterVariable(&r_drawparticles);
2454         Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2455         Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2456         Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2457         Cvar_RegisterVariable(&r_drawdecals);
2458         Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2459         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2460 }
2461
2462 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2463 {
2464         int surfacelistindex;
2465         const decal_t *d;
2466         float *v3f, *t2f, *c4f;
2467         particletexture_t *tex;
2468         vec_t right[3], up[3], size, ca;
2469         float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2470
2471         RSurf_ActiveWorldEntity();
2472
2473         r_refdef.stats[r_stat_drawndecals] += numsurfaces;
2474 //      R_Mesh_ResetTextureState();
2475         GL_DepthMask(false);
2476         GL_DepthRange(0, 1);
2477         GL_PolygonOffset(0, 0);
2478         GL_DepthTest(true);
2479         GL_CullFace(GL_NONE);
2480
2481         // generate all the vertices at once
2482         for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2483         {
2484                 d = cl.decals + surfacelist[surfacelistindex];
2485
2486                 // calculate color
2487                 c4f = particle_color4f + 16*surfacelistindex;
2488                 ca = d->alpha * alphascale;
2489                 // ensure alpha multiplier saturates properly
2490                 if (ca > 1.0f / 256.0f)
2491                         ca = 1.0f / 256.0f;     
2492                 if (r_refdef.fogenabled)
2493                         ca *= RSurf_FogVertex(d->org);
2494                 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2495                 Vector4Copy(c4f, c4f + 4);
2496                 Vector4Copy(c4f, c4f + 8);
2497                 Vector4Copy(c4f, c4f + 12);
2498
2499                 // calculate vertex positions
2500                 size = d->size * cl_particles_size.value;
2501                 VectorVectors(d->normal, right, up);
2502                 VectorScale(right, size, right);
2503                 VectorScale(up, size, up);
2504                 v3f = particle_vertex3f + 12*surfacelistindex;
2505                 v3f[ 0] = d->org[0] - right[0] - up[0];
2506                 v3f[ 1] = d->org[1] - right[1] - up[1];
2507                 v3f[ 2] = d->org[2] - right[2] - up[2];
2508                 v3f[ 3] = d->org[0] - right[0] + up[0];
2509                 v3f[ 4] = d->org[1] - right[1] + up[1];
2510                 v3f[ 5] = d->org[2] - right[2] + up[2];
2511                 v3f[ 6] = d->org[0] + right[0] + up[0];
2512                 v3f[ 7] = d->org[1] + right[1] + up[1];
2513                 v3f[ 8] = d->org[2] + right[2] + up[2];
2514                 v3f[ 9] = d->org[0] + right[0] - up[0];
2515                 v3f[10] = d->org[1] + right[1] - up[1];
2516                 v3f[11] = d->org[2] + right[2] - up[2];
2517
2518                 // calculate texcoords
2519                 tex = &particletexture[d->texnum];
2520                 t2f = particle_texcoord2f + 8*surfacelistindex;
2521                 t2f[0] = tex->s1;t2f[1] = tex->t2;
2522                 t2f[2] = tex->s1;t2f[3] = tex->t1;
2523                 t2f[4] = tex->s2;t2f[5] = tex->t1;
2524                 t2f[6] = tex->s2;t2f[7] = tex->t2;
2525         }
2526
2527         // now render the decals all at once
2528         // (this assumes they all use one particle font texture!)
2529         GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2530         R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2531         R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2532         R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2533 }
2534
2535 void R_DrawDecals (void)
2536 {
2537         int i;
2538         int drawdecals = r_drawdecals.integer;
2539         decal_t *decal;
2540         float frametime;
2541         float decalfade;
2542         float drawdist2;
2543         int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2544
2545         frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2546         cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2547
2548         // LordHavoc: early out conditions
2549         if (!cl.num_decals)
2550                 return;
2551
2552         decalfade = frametime * 256 / cl_decals_fadetime.value;
2553         drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2554         drawdist2 = drawdist2*drawdist2;
2555
2556         for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2557         {
2558                 if (!decal->typeindex)
2559                         continue;
2560
2561                 if (killsequence - decal->decalsequence > 0)
2562                         goto killdecal;
2563
2564                 if (cl.time > decal->time2 + cl_decals_time.value)
2565                 {
2566                         decal->alpha -= decalfade;
2567                         if (decal->alpha <= 0)
2568                                 goto killdecal;
2569                 }
2570
2571                 if (decal->owner)
2572                 {
2573                         if (cl.entities[decal->owner].render.model == decal->ownermodel)
2574                         {
2575                                 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2576                                 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2577                         }
2578                         else
2579                                 goto killdecal;
2580                 }
2581
2582                 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2583                         continue;
2584
2585                 if (!drawdecals)
2586                         continue;
2587
2588                 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))
2589                         R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2590                 continue;
2591 killdecal:
2592                 decal->typeindex = 0;
2593                 if (cl.free_decal > i)
2594                         cl.free_decal = i;
2595         }
2596
2597         // reduce cl.num_decals if possible
2598         while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2599                 cl.num_decals--;
2600
2601         if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2602         {
2603                 decal_t *olddecals = cl.decals;
2604                 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2605                 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2606                 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2607                 Mem_Free(olddecals);
2608         }
2609
2610         r_refdef.stats[r_stat_totaldecals] = cl.num_decals;
2611 }
2612
2613 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2614 {
2615         vec3_t vecorg, vecvel, baseright, baseup;
2616         int surfacelistindex;
2617         int batchstart, batchcount;
2618         const particle_t *p;
2619         pblend_t blendmode;
2620         rtexture_t *texture;
2621         float *v3f, *t2f, *c4f;
2622         particletexture_t *tex;
2623         float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2624 //      float ambient[3], diffuse[3], diffusenormal[3];
2625         float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2626         vec4_t colormultiplier;
2627         float minparticledist_start, minparticledist_end;
2628         qboolean dofade;
2629
2630         RSurf_ActiveWorldEntity();
2631
2632         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));
2633
2634         r_refdef.stats[r_stat_particles] += numsurfaces;
2635 //      R_Mesh_ResetTextureState();
2636         GL_DepthMask(false);
2637         GL_DepthRange(0, 1);
2638         GL_PolygonOffset(0, 0);
2639         GL_DepthTest(true);
2640         GL_CullFace(GL_NONE);
2641
2642         spintime = r_refdef.scene.time;
2643
2644         minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2645         minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2646         dofade = (minparticledist_start < minparticledist_end);
2647
2648         // first generate all the vertices at once
2649         for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2650         {
2651                 p = cl.particles + surfacelist[surfacelistindex];
2652
2653                 blendmode = (pblend_t)p->blendmode;
2654                 palpha = p->alpha;
2655                 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2656                         palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward)  - minparticledist_start) / (minparticledist_end - minparticledist_start));
2657                 alpha = palpha * colormultiplier[3];
2658                 // ensure alpha multiplier saturates properly
2659                 if (alpha > 1.0f)
2660                         alpha = 1.0f;
2661
2662                 switch (blendmode)
2663                 {
2664                 case PBLEND_INVALID:
2665                 case PBLEND_INVMOD:
2666                         // additive and modulate can just fade out in fog (this is correct)
2667                         if (r_refdef.fogenabled)
2668                                 alpha *= RSurf_FogVertex(p->org);
2669                         // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2670                         alpha *= 1.0f / 256.0f;
2671                         c4f[0] = p->color[0] * alpha;
2672                         c4f[1] = p->color[1] * alpha;
2673                         c4f[2] = p->color[2] * alpha;
2674                         c4f[3] = 0;
2675                         break;
2676                 case PBLEND_ADD:
2677                         // additive and modulate can just fade out in fog (this is correct)
2678                         if (r_refdef.fogenabled)
2679                                 alpha *= RSurf_FogVertex(p->org);
2680                         // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2681                         c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2682                         c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2683                         c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2684                         c4f[3] = 0;
2685                         break;
2686                 case PBLEND_ALPHA:
2687                         c4f[0] = p->color[0] * colormultiplier[0];
2688                         c4f[1] = p->color[1] * colormultiplier[1];
2689                         c4f[2] = p->color[2] * colormultiplier[2];
2690                         c4f[3] = alpha;
2691                         // note: lighting is not cheap!
2692                         if (particletype[p->typeindex].lighting)
2693                         {
2694                                 vecorg[0] = p->org[0];
2695                                 vecorg[1] = p->org[1];
2696                                 vecorg[2] = p->org[2];
2697                                 R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2698                         }
2699                         // mix in the fog color
2700                         if (r_refdef.fogenabled)
2701                         {
2702                                 fog = RSurf_FogVertex(p->org);
2703                                 ifog = 1 - fog;
2704                                 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2705                                 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2706                                 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2707                         }
2708                         // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2709                         VectorScale(c4f, alpha, c4f);
2710                         break;
2711                 }
2712                 // copy the color into the other three vertices
2713                 Vector4Copy(c4f, c4f + 4);
2714                 Vector4Copy(c4f, c4f + 8);
2715                 Vector4Copy(c4f, c4f + 12);
2716
2717                 size = p->size * cl_particles_size.value;
2718                 tex = &particletexture[p->texnum];
2719                 switch(p->orientation)
2720                 {
2721 //              case PARTICLE_INVALID:
2722                 case PARTICLE_BILLBOARD:
2723                         if (p->angle + p->spin)
2724                         {
2725                                 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2726                                 spinsin = sin(spinrad) * size;
2727                                 spincos = cos(spinrad) * size;
2728                                 spinm1 = -p->stretch * spincos;
2729                                 spinm2 = -spinsin;
2730                                 spinm3 = spinsin;
2731                                 spinm4 = -p->stretch * spincos;
2732                                 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2733                                 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2734                         }
2735                         else
2736                         {
2737                                 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2738                                 VectorScale(r_refdef.view.up, size, up);
2739                         }
2740
2741                         v3f[ 0] = p->org[0] - right[0] - up[0];
2742                         v3f[ 1] = p->org[1] - right[1] - up[1];
2743                         v3f[ 2] = p->org[2] - right[2] - up[2];
2744                         v3f[ 3] = p->org[0] - right[0] + up[0];
2745                         v3f[ 4] = p->org[1] - right[1] + up[1];
2746                         v3f[ 5] = p->org[2] - right[2] + up[2];
2747                         v3f[ 6] = p->org[0] + right[0] + up[0];
2748                         v3f[ 7] = p->org[1] + right[1] + up[1];
2749                         v3f[ 8] = p->org[2] + right[2] + up[2];
2750                         v3f[ 9] = p->org[0] + right[0] - up[0];
2751                         v3f[10] = p->org[1] + right[1] - up[1];
2752                         v3f[11] = p->org[2] + right[2] - up[2];
2753                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2754                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2755                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2756                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2757                         break;
2758                 case PARTICLE_ORIENTED_DOUBLESIDED:
2759                         vecvel[0] = p->vel[0];
2760                         vecvel[1] = p->vel[1];
2761                         vecvel[2] = p->vel[2];
2762                         VectorVectors(vecvel, baseright, baseup);
2763                         if (p->angle + p->spin)
2764                         {
2765                                 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2766                                 spinsin = sin(spinrad) * size;
2767                                 spincos = cos(spinrad) * size;
2768                                 spinm1 = p->stretch * spincos;
2769                                 spinm2 = -spinsin;
2770                                 spinm3 = spinsin;
2771                                 spinm4 = p->stretch * spincos;
2772                                 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2773                                 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2774                         }
2775                         else
2776                         {
2777                                 VectorScale(baseright, size * p->stretch, right);
2778                                 VectorScale(baseup, size, up);
2779                         }
2780                         v3f[ 0] = p->org[0] - right[0] - up[0];
2781                         v3f[ 1] = p->org[1] - right[1] - up[1];
2782                         v3f[ 2] = p->org[2] - right[2] - up[2];
2783                         v3f[ 3] = p->org[0] - right[0] + up[0];
2784                         v3f[ 4] = p->org[1] - right[1] + up[1];
2785                         v3f[ 5] = p->org[2] - right[2] + up[2];
2786                         v3f[ 6] = p->org[0] + right[0] + up[0];
2787                         v3f[ 7] = p->org[1] + right[1] + up[1];
2788                         v3f[ 8] = p->org[2] + right[2] + up[2];
2789                         v3f[ 9] = p->org[0] + right[0] - up[0];
2790                         v3f[10] = p->org[1] + right[1] - up[1];
2791                         v3f[11] = p->org[2] + right[2] - up[2];
2792                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2793                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2794                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2795                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2796                         break;
2797                 case PARTICLE_SPARK:
2798                         len = VectorLength(p->vel);
2799                         VectorNormalize2(p->vel, up);
2800                         lenfactor = p->stretch * 0.04 * len;
2801                         if(lenfactor < size * 0.5)
2802                                 lenfactor = size * 0.5;
2803                         VectorMA(p->org, -lenfactor, up, v);
2804                         VectorMA(p->org,  lenfactor, up, up2);
2805                         R_CalcBeam_Vertex3f(v3f, v, up2, size);
2806                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2807                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2808                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2809                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2810                         break;
2811                 case PARTICLE_VBEAM:
2812                         R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2813                         VectorSubtract(p->vel, p->org, up);
2814                         VectorNormalize(up);
2815                         v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2816                         v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2817                         t2f[0] = tex->s2;t2f[1] = v[0];
2818                         t2f[2] = tex->s1;t2f[3] = v[0];
2819                         t2f[4] = tex->s1;t2f[5] = v[1];
2820                         t2f[6] = tex->s2;t2f[7] = v[1];
2821                         break;
2822                 case PARTICLE_HBEAM:
2823                         R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2824                         VectorSubtract(p->vel, p->org, up);
2825                         VectorNormalize(up);
2826                         v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2827                         v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2828                         t2f[0] = v[0];t2f[1] = tex->t1;
2829                         t2f[2] = v[0];t2f[3] = tex->t2;
2830                         t2f[4] = v[1];t2f[5] = tex->t2;
2831                         t2f[6] = v[1];t2f[7] = tex->t1;
2832                         break;
2833                 }
2834         }
2835
2836         // now render batches of particles based on blendmode and texture
2837         blendmode = PBLEND_INVALID;
2838         texture = NULL;
2839         batchstart = 0;
2840         batchcount = 0;
2841         R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2842         for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2843         {
2844                 p = cl.particles + surfacelist[surfacelistindex];
2845
2846                 if (texture != particletexture[p->texnum].texture)
2847                 {
2848                         texture = particletexture[p->texnum].texture;
2849                         R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2850                 }
2851
2852                 if (p->blendmode == PBLEND_INVMOD)
2853                 {
2854                         // inverse modulate blend - group these
2855                         GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2856                         // iterate until we find a change in settings
2857                         batchstart = surfacelistindex++;
2858                         for (;surfacelistindex < numsurfaces;surfacelistindex++)
2859                         {
2860                                 p = cl.particles + surfacelist[surfacelistindex];
2861                                 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2862                                         break;
2863                         }
2864                 }
2865                 else
2866                 {
2867                         // additive or alpha blend - group these
2868                         // (we can group these because we premultiplied the texture alpha)
2869                         GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2870                         // iterate until we find a change in settings
2871                         batchstart = surfacelistindex++;
2872                         for (;surfacelistindex < numsurfaces;surfacelistindex++)
2873                         {
2874                                 p = cl.particles + surfacelist[surfacelistindex];
2875                                 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2876                                         break;
2877                         }
2878                 }
2879
2880                 batchcount = surfacelistindex - batchstart;
2881                 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2882         }
2883 }
2884
2885 void R_DrawParticles (void)
2886 {
2887         int i, a;
2888         int drawparticles = r_drawparticles.integer;
2889         float minparticledist_start;
2890         particle_t *p;
2891         float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2892         float drawdist2;
2893         int hitent;
2894         trace_t trace;
2895         qboolean update;
2896
2897         frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2898         cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2899
2900         // LordHavoc: early out conditions
2901         if (!cl.num_particles)
2902                 return;
2903
2904         minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2905         gravity = frametime * cl.movevars_gravity;
2906         update = frametime > 0;
2907         drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2908         drawdist2 = drawdist2*drawdist2;
2909
2910         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2911         {
2912                 if (!p->typeindex)
2913                 {
2914                         if (cl.free_particle > i)
2915                                 cl.free_particle = i;
2916                         continue;
2917                 }
2918
2919                 if (update)
2920                 {
2921                         if (p->delayedspawn > cl.time)
2922                                 continue;
2923
2924                         p->size += p->sizeincrease * frametime;
2925                         p->alpha -= p->alphafade * frametime;
2926
2927                         if (p->alpha <= 0 || p->die <= cl.time)
2928                                 goto killparticle;
2929
2930                         if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2931                         {
2932                                 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2933                                 {
2934                                         if (p->typeindex == pt_blood)
2935                                                 p->size += frametime * 8;
2936                                         else
2937                                                 p->vel[2] -= p->gravity * gravity;
2938                                         f = 1.0f - min(p->liquidfriction * frametime, 1);
2939                                         VectorScale(p->vel, f, p->vel);
2940                                 }
2941                                 else
2942                                 {
2943                                         p->vel[2] -= p->gravity * gravity;
2944                                         if (p->airfriction)
2945                                         {
2946                                                 f = 1.0f - min(p->airfriction * frametime, 1);
2947                                                 VectorScale(p->vel, f, p->vel);
2948                                         }
2949                                 }
2950
2951                                 VectorCopy(p->org, oldorg);
2952                                 VectorMA(p->org, frametime, p->vel, p->org);
2953 //                              if (p->bounce && cl.time >= p->delayedcollisions)
2954                                 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2955                                 {
2956                                         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);
2957                                         // if the trace started in or hit something of SUPERCONTENTS_NODROP
2958                                         // or if the trace hit something flagged as NOIMPACT
2959                                         // then remove the particle
2960                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2961                                                 goto killparticle;
2962                                         VectorCopy(trace.endpos, p->org);
2963                                         // react if the particle hit something
2964                                         if (trace.fraction < 1)
2965                                         {
2966                                                 VectorCopy(trace.endpos, p->org);
2967
2968                                                 if (p->staintexnum >= 0)
2969                                                 {
2970                                                         // blood - splash on solid
2971                                                         if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2972                                                         {
2973                                                                 R_Stain(p->org, 16,
2974                                                                         p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2975                                                                         p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2976                                                                 if (cl_decals.integer)
2977                                                                 {
2978                                                                         // create a decal for the blood splat
2979                                                                         a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2980                                                                         if (cl_decals_newsystem_bloodsmears.integer)
2981                                                                         {
2982                                                                                 VectorCopy(p->vel, decaldir);
2983                                                                                 VectorNormalize(decaldir);
2984                                                                         }
2985                                                                         else
2986                                                                                 VectorCopy(trace.plane.normal, decaldir);
2987                                                                         CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2988                                                                 }
2989                                                         }
2990                                                 }
2991
2992                                                 if (p->typeindex == pt_blood)
2993                                                 {
2994                                                         // blood - splash on solid
2995                                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2996                                                                 goto killparticle;
2997                                                         if(p->staintexnum == -1) // staintex < -1 means no stains at all
2998                                                         {
2999                                                                 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)));
3000                                                                 if (cl_decals.integer)
3001                                                                 {
3002                                                                         // create a decal for the blood splat
3003                                                                         if (cl_decals_newsystem_bloodsmears.integer)
3004                                                                         {
3005                                                                                 VectorCopy(p->vel, decaldir);
3006                                                                                 VectorNormalize(decaldir);
3007                                                                         }
3008                                                                         else
3009                                                                                 VectorCopy(trace.plane.normal, decaldir);
3010                                                                         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);
3011                                                                 }
3012                                                         }
3013                                                         goto killparticle;
3014                                                 }
3015                                                 else if (p->bounce < 0)
3016                                                 {
3017                                                         // bounce -1 means remove on impact
3018                                                         goto killparticle;
3019                                                 }
3020                                                 else
3021                                                 {
3022                                                         // anything else - bounce off solid
3023                                                         dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
3024                                                         VectorMA(p->vel, dist, trace.plane.normal, p->vel);
3025                                                 }
3026                                         }
3027                                 }
3028
3029                                 if (VectorLength2(p->vel) < 0.03)
3030                                 {
3031                                         if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
3032                                                 goto killparticle;
3033                                         VectorClear(p->vel);
3034                                 }
3035                         }
3036
3037                         if (p->typeindex != pt_static)
3038                         {
3039                                 switch (p->typeindex)
3040                                 {
3041                                 case pt_entityparticle:
3042                                         // particle that removes itself after one rendered frame
3043                                         if (p->time2)
3044                                                 goto killparticle;
3045                                         else
3046                                                 p->time2 = 1;
3047                                         break;
3048                                 case pt_blood:
3049                                         a = CL_PointSuperContents(p->org);
3050                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
3051                                                 goto killparticle;
3052                                         break;
3053                                 case pt_bubble:
3054                                         a = CL_PointSuperContents(p->org);
3055                                         if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
3056                                                 goto killparticle;
3057                                         break;
3058                                 case pt_rain:
3059                                         a = CL_PointSuperContents(p->org);
3060                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3061                                                 goto killparticle;
3062                                         break;
3063                                 case pt_snow:
3064                                         if (cl.time > p->time2)
3065                                         {
3066                                                 // snow flutter
3067                                                 p->time2 = cl.time + (rand() & 3) * 0.1;
3068                                                 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3069                                                 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3070                                         }
3071                                         a = CL_PointSuperContents(p->org);
3072                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3073                                                 goto killparticle;
3074                                         break;
3075                                 default:
3076                                         break;
3077                                 }
3078                         }
3079                 }
3080                 else if (p->delayedspawn > cl.time)
3081                         continue;
3082                 if (!drawparticles)
3083                         continue;
3084                 // don't render particles too close to the view (they chew fillrate)
3085                 // also don't render particles behind the view (useless)
3086                 // further checks to cull to the frustum would be too slow here
3087                 switch(p->typeindex)
3088                 {
3089                 case pt_beam:
3090                         // beams have no culling
3091                         R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3092                         break;
3093                 default:
3094                         if(cl_particles_visculling.integer)
3095                                 if (!r_refdef.viewcache.world_novis)
3096                                         if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3097                                         {
3098                                                 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3099                                                 if(leaf)
3100                                                         if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3101                                                                 continue;
3102                                         }
3103                         // anything else just has to be in front of the viewer and visible at this distance
3104                         if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3105                                 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3106                         break;
3107                 }
3108
3109                 continue;
3110 killparticle:
3111                 p->typeindex = 0;
3112                 if (cl.free_particle > i)
3113                         cl.free_particle = i;
3114         }
3115
3116         // reduce cl.num_particles if possible
3117         while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3118                 cl.num_particles--;
3119
3120         if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3121         {
3122                 particle_t *oldparticles = cl.particles;
3123                 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3124                 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3125                 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3126                 Mem_Free(oldparticles);
3127         }
3128 }