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