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