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