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