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