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