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