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