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