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