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