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