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