]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_particles.c
added r_trippy cvar
[xonotic/darkplaces.git] / cl_particles.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "quakedef.h"
22
23 #include "cl_collision.h"
24 #include "image.h"
25 #include "r_shadow.h"
26
27 // 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         if (vid.sRGB3D)
606         {
607                 part->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[0]) * 256.0f);
608                 part->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[1]) * 256.0f);
609                 part->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[2]) * 256.0f);
610         }
611         part->alpha = palpha;
612         part->alphafade = palphafade;
613         part->staintexnum = staintex;
614         if(staincolor1 >= 0 && staincolor2 >= 0)
615         {
616                 l2 = (int)lhrandom(0.5, 256.5);
617                 l1 = 256 - l2;
618                 if(blendmode == PBLEND_INVMOD)
619                 {
620                         r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
621                         g = ((((staincolor1 >>  8) & 0xFF) * l1 + ((staincolor2 >>  8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
622                         b = ((((staincolor1 >>  0) & 0xFF) * l1 + ((staincolor2 >>  0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
623                 }
624                 else
625                 {
626                         r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
627                         g = ((((staincolor1 >>  8) & 0xFF) * l1 + ((staincolor2 >>  8) & 0xFF) * l2) * part->color[1]) / 0x8000;
628                         b = ((((staincolor1 >>  0) & 0xFF) * l1 + ((staincolor2 >>  0) & 0xFF) * l2) * part->color[2]) / 0x8000;
629                 }
630                 if(r > 0xFF) r = 0xFF;
631                 if(g > 0xFF) g = 0xFF;
632                 if(b > 0xFF) b = 0xFF;
633         }
634         else
635         {
636                 r = part->color[0]; // -1 is shorthand for stain = particle color
637                 g = part->color[1];
638                 b = part->color[2];
639         }
640         part->staincolor[0] = r;
641         part->staincolor[1] = g;
642         part->staincolor[2] = b;
643         part->stainalpha = palpha * stainalpha;
644         part->stainsize = psize * stainsize;
645         if(tint)
646         {
647                 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
648                 {
649                         part->color[0] *= tint[0];
650                         part->color[1] *= tint[1];
651                         part->color[2] *= tint[2];
652                 }
653                 part->alpha *= tint[3];
654                 part->alphafade *= tint[3];
655                 part->stainalpha *= tint[3];
656         }
657         part->texnum = ptex;
658         part->size = psize;
659         part->sizeincrease = psizeincrease;
660         part->gravity = pgravity;
661         part->bounce = pbounce;
662         part->stretch = stretch;
663         VectorRandom(v);
664         part->org[0] = px + originjitter * v[0];
665         part->org[1] = py + originjitter * v[1];
666         part->org[2] = pz + originjitter * v[2];
667         part->vel[0] = pvx + velocityjitter * v[0];
668         part->vel[1] = pvy + velocityjitter * v[1];
669         part->vel[2] = pvz + velocityjitter * v[2];
670         part->time2 = 0;
671         part->airfriction = pairfriction;
672         part->liquidfriction = pliquidfriction;
673         part->die = cl.time + lifetime;
674         part->delayedspawn = cl.time;
675 //      part->delayedcollisions = 0;
676         part->qualityreduction = pqualityreduction;
677         part->angle = angle;
678         part->spin = spin;
679         // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
680         if (part->typeindex == pt_rain)
681         {
682                 int i;
683                 particle_t *part2;
684                 float lifetime = part->die - cl.time;
685                 vec3_t endvec;
686                 trace_t trace;
687                 // turn raindrop into simple spark and create delayedspawn splash effect
688                 part->typeindex = pt_spark;
689                 part->bounce = 0;
690                 VectorMA(part->org, lifetime, part->vel, endvec);
691                 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
692                 part->die = cl.time + lifetime * trace.fraction;
693                 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);
694                 if (part2)
695                 {
696                         part2->delayedspawn = part->die;
697                         part2->die += part->die - cl.time;
698                         for (i = rand() & 7;i < 10;i++)
699                         {
700                                 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);
701                                 if (part2)
702                                 {
703                                         part2->delayedspawn = part->die;
704                                         part2->die += part->die - cl.time;
705                                 }
706                         }
707                 }
708         }
709 #if 0
710         else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
711         {
712                 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
713                 vec3_t endvec;
714                 trace_t trace;
715                 VectorMA(part->org, lifetime, part->vel, endvec);
716                 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
717                 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
718         }
719 #endif
720
721         return part;
722 }
723
724 static void CL_ImmediateBloodStain(particle_t *part)
725 {
726         vec3_t v;
727         int staintex;
728
729         // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
730         if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
731         {
732                 VectorCopy(part->vel, v);
733                 VectorNormalize(v);
734                 staintex = part->staintexnum;
735                 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);
736         }
737
738         // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
739         if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
740         {
741                 VectorCopy(part->vel, v);
742                 VectorNormalize(v);
743                 staintex = tex_blooddecal[rand()&7];
744                 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);
745         }
746 }
747
748 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
749 {
750         int l1, l2;
751         decal_t *decal;
752         entity_render_t *ent = &cl.entities[hitent].render;
753         unsigned char color[3];
754         if (!cl_decals.integer)
755                 return;
756         if (!ent->allowdecals)
757                 return;
758
759         l2 = (int)lhrandom(0.5, 256.5);
760         l1 = 256 - l2;
761         color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
762         color[1] = ((((color1 >>  8) & 0xFF) * l1 + ((color2 >>  8) & 0xFF) * l2) >> 8) & 0xFF;
763         color[2] = ((((color1 >>  0) & 0xFF) * l1 + ((color2 >>  0) & 0xFF) * l2) >> 8) & 0xFF;
764
765         if (cl_decals_newsystem.integer)
766         {
767                 if (vid.sRGB3D)
768                         R_DecalSystem_SplatEntities(org, normal, Image_LinearFloatFromsRGB(color[0]), Image_LinearFloatFromsRGB(color[1]), Image_LinearFloatFromsRGB(color[2]), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
769                 else
770                         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);
771                 return;
772         }
773
774         for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
775         if (cl.free_decal >= cl.max_decals)
776                 return;
777         decal = &cl.decals[cl.free_decal++];
778         if (cl.num_decals < cl.free_decal)
779                 cl.num_decals = cl.free_decal;
780         memset(decal, 0, sizeof(*decal));
781         decal->decalsequence = cl.decalsequence++;
782         decal->typeindex = pt_decal;
783         decal->texnum = texnum;
784         VectorMA(org, cl_decals_bias.value, normal, decal->org);
785         VectorCopy(normal, decal->normal);
786         decal->size = size;
787         decal->alpha = alpha;
788         decal->time2 = cl.time;
789         decal->color[0] = color[0];
790         decal->color[1] = color[1];
791         decal->color[2] = color[2];
792         if (vid.sRGB3D)
793         {
794                 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
795                 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
796                 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
797         }
798         decal->owner = hitent;
799         decal->clusterindex = -1000; // no vis culling unless we're sure
800         if (hitent)
801         {
802                 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
803                 decal->ownermodel = cl.entities[decal->owner].render.model;
804                 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
805                 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
806         }
807         else
808         {
809                 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
810                 {
811                         mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
812                         if(leaf)
813                                 decal->clusterindex = leaf->clusterindex;
814                 }
815         }
816 }
817
818 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
819 {
820         int i;
821         float bestfrac, bestorg[3], bestnormal[3];
822         float org2[3];
823         int besthitent = 0, hitent;
824         trace_t trace;
825         bestfrac = 10;
826         for (i = 0;i < 32;i++)
827         {
828                 VectorRandom(org2);
829                 VectorMA(org, maxdist, org2, org2);
830                 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
831                 // take the closest trace result that doesn't end up hitting a NOMARKS
832                 // surface (sky for example)
833                 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
834                 {
835                         bestfrac = trace.fraction;
836                         besthitent = hitent;
837                         VectorCopy(trace.endpos, bestorg);
838                         VectorCopy(trace.plane.normal, bestnormal);
839                 }
840         }
841         if (bestfrac < 1)
842                 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
843 }
844
845 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
846 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
847 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)
848 {
849         vec3_t center;
850         matrix4x4_t tempmatrix;
851         particle_t *part;
852         VectorLerp(originmins, 0.5, originmaxs, center);
853         Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
854         if (effectnameindex == EFFECT_SVC_PARTICLE)
855         {
856                 if (cl_particles.integer)
857                 {
858                         // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
859                         if (count == 1024)
860                                 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
861                         else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
862                                 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
863                         else
864                         {
865                                 count *= cl_particles_quality.value;
866                                 for (;count > 0;count--)
867                                 {
868                                         int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
869                                         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);
870                                 }
871                         }
872                 }
873         }
874         else if (effectnameindex == EFFECT_TE_WIZSPIKE)
875                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
876         else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
877                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
878         else if (effectnameindex == EFFECT_TE_SPIKE)
879         {
880                 if (cl_particles_bulletimpacts.integer)
881                 {
882                         if (cl_particles_quake.integer)
883                         {
884                                 if (cl_particles_smoke.integer)
885                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
886                         }
887                         else
888                         {
889                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
890                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
891                                 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);
892                         }
893                 }
894                 // bullet hole
895                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
896                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
897         }
898         else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
899         {
900                 if (cl_particles_bulletimpacts.integer)
901                 {
902                         if (cl_particles_quake.integer)
903                         {
904                                 if (cl_particles_smoke.integer)
905                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
906                         }
907                         else
908                         {
909                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
910                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
911                                 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);
912                         }
913                 }
914                 // bullet hole
915                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
916                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
917                 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);
918         }
919         else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
920         {
921                 if (cl_particles_bulletimpacts.integer)
922                 {
923                         if (cl_particles_quake.integer)
924                         {
925                                 if (cl_particles_smoke.integer)
926                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
927                         }
928                         else
929                         {
930                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
931                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
932                                 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);
933                         }
934                 }
935                 // bullet hole
936                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
937                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
938         }
939         else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
940         {
941                 if (cl_particles_bulletimpacts.integer)
942                 {
943                         if (cl_particles_quake.integer)
944                         {
945                                 if (cl_particles_smoke.integer)
946                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
947                         }
948                         else
949                         {
950                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
951                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
952                                 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);
953                         }
954                 }
955                 // bullet hole
956                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
957                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
958                 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);
959         }
960         else if (effectnameindex == EFFECT_TE_BLOOD)
961         {
962                 if (!cl_particles_blood.integer)
963                         return;
964                 if (cl_particles_quake.integer)
965                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
966                 else
967                 {
968                         static double bloodaccumulator = 0;
969                         qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
970                         //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);
971                         bloodaccumulator += count * 0.333 * cl_particles_quality.value;
972                         for (;bloodaccumulator > 0;bloodaccumulator--)
973                         {
974                                 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);
975                                 if (immediatebloodstain && part)
976                                 {
977                                         immediatebloodstain = false;
978                                         CL_ImmediateBloodStain(part);
979                                 }
980                         }
981                 }
982         }
983         else if (effectnameindex == EFFECT_TE_SPARK)
984                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
985         else if (effectnameindex == EFFECT_TE_PLASMABURN)
986         {
987                 // plasma scorch mark
988                 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
989                 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
990                 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
991         }
992         else if (effectnameindex == EFFECT_TE_GUNSHOT)
993         {
994                 if (cl_particles_bulletimpacts.integer)
995                 {
996                         if (cl_particles_quake.integer)
997                                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
998                         else
999                         {
1000                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1001                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1002                                 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);
1003                         }
1004                 }
1005                 // bullet hole
1006                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1007                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1008         }
1009         else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1010         {
1011                 if (cl_particles_bulletimpacts.integer)
1012                 {
1013                         if (cl_particles_quake.integer)
1014                                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1015                         else
1016                         {
1017                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1018                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1019                                 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);
1020                         }
1021                 }
1022                 // bullet hole
1023                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1024                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1025                 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);
1026         }
1027         else if (effectnameindex == EFFECT_TE_EXPLOSION)
1028         {
1029                 CL_ParticleExplosion(center);
1030                 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);
1031         }
1032         else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1033         {
1034                 CL_ParticleExplosion(center);
1035                 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);
1036         }
1037         else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1038         {
1039                 if (cl_particles_quake.integer)
1040                 {
1041                         int i;
1042                         for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1043                         {
1044                                 if (i & 1)
1045                                         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);
1046                                 else
1047                                         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);
1048                         }
1049                 }
1050                 else
1051                         CL_ParticleExplosion(center);
1052                 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);
1053         }
1054         else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1055                 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);
1056         else if (effectnameindex == EFFECT_TE_FLAMEJET)
1057         {
1058                 count *= cl_particles_quality.value;
1059                 while (count-- > 0)
1060                         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);
1061         }
1062         else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1063         {
1064                 float i, j, inc, vel;
1065                 vec3_t dir, org;
1066
1067                 inc = 8 / cl_particles_quality.value;
1068                 for (i = -128;i < 128;i += inc)
1069                 {
1070                         for (j = -128;j < 128;j += inc)
1071                         {
1072                                 dir[0] = j + lhrandom(0, inc);
1073                                 dir[1] = i + lhrandom(0, inc);
1074                                 dir[2] = 256;
1075                                 org[0] = center[0] + dir[0];
1076                                 org[1] = center[1] + dir[1];
1077                                 org[2] = center[2] + lhrandom(0, 64);
1078                                 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1079                                 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);
1080                         }
1081                 }
1082         }
1083         else if (effectnameindex == EFFECT_TE_TELEPORT)
1084         {
1085                 float i, j, k, inc, vel;
1086                 vec3_t dir;
1087
1088                 if (cl_particles_quake.integer)
1089                         inc = 4 / cl_particles_quality.value;
1090                 else
1091                         inc = 8 / cl_particles_quality.value;
1092                 for (i = -16;i < 16;i += inc)
1093                 {
1094                         for (j = -16;j < 16;j += inc)
1095                         {
1096                                 for (k = -24;k < 32;k += inc)
1097                                 {
1098                                         VectorSet(dir, i*8, j*8, k*8);
1099                                         VectorNormalize(dir);
1100                                         vel = lhrandom(50, 113);
1101                                         if (cl_particles_quake.integer)
1102                                                 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);
1103                                         else
1104                                                 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);
1105                                 }
1106                         }
1107                 }
1108                 if (!cl_particles_quake.integer)
1109                         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);
1110                 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);
1111         }
1112         else if (effectnameindex == EFFECT_TE_TEI_G3)
1113                 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);
1114         else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1115         {
1116                 if (cl_particles_smoke.integer)
1117                 {
1118                         count *= 0.25f * cl_particles_quality.value;
1119                         while (count-- > 0)
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, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1121                 }
1122         }
1123         else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1124         {
1125                 CL_ParticleExplosion(center);
1126                 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);
1127         }
1128         else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1129         {
1130                 float f;
1131                 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1132                 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1133                 if (cl_particles_smoke.integer)
1134                         for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1135                                 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);
1136                 if (cl_particles_sparks.integer)
1137                         for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1138                                 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);
1139                 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);
1140         }
1141         else if (effectnameindex == EFFECT_EF_FLAME)
1142         {
1143                 count *= 300 * cl_particles_quality.value;
1144                 while (count-- > 0)
1145                         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);
1146                 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);
1147         }
1148         else if (effectnameindex == EFFECT_EF_STARDUST)
1149         {
1150                 count *= 200 * cl_particles_quality.value;
1151                 while (count-- > 0)
1152                         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);
1153                 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);
1154         }
1155         else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1156         {
1157                 vec3_t dir, pos;
1158                 float len, dec, qd;
1159                 int smoke, blood, bubbles, r, color;
1160
1161                 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1162                 {
1163                         vec4_t light;
1164                         Vector4Set(light, 0, 0, 0, 0);
1165
1166                         if (effectnameindex == EFFECT_TR_ROCKET)
1167                                 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1168                         else if (effectnameindex == EFFECT_TR_VORESPIKE)
1169                         {
1170                                 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1171                                         Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1172                                 else
1173                                         Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1174                         }
1175                         else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1176                                 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1177
1178                         if (light[3])
1179                         {
1180                                 matrix4x4_t tempmatrix;
1181                                 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1182                                 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);
1183                                 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1184                         }
1185                 }
1186
1187                 if (!spawnparticles)
1188                         return;
1189
1190                 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1191                         return;
1192
1193                 VectorSubtract(originmaxs, originmins, dir);
1194                 len = VectorNormalizeLength(dir);
1195                 if (ent)
1196                 {
1197                         dec = -ent->persistent.trail_time;
1198                         ent->persistent.trail_time += len;
1199                         if (ent->persistent.trail_time < 0.01f)
1200                                 return;
1201
1202                         // if we skip out, leave it reset
1203                         ent->persistent.trail_time = 0.0f;
1204                 }
1205                 else
1206                         dec = 0;
1207
1208                 // advance into this frame to reach the first puff location
1209                 VectorMA(originmins, dec, dir, pos);
1210                 len -= dec;
1211
1212                 smoke = cl_particles.integer && cl_particles_smoke.integer;
1213                 blood = cl_particles.integer && cl_particles_blood.integer;
1214                 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1215                 qd = 1.0f / cl_particles_quality.value;
1216
1217                 while (len >= 0)
1218                 {
1219                         dec = 3;
1220                         if (blood)
1221                         {
1222                                 if (effectnameindex == EFFECT_TR_BLOOD)
1223                                 {
1224                                         if (cl_particles_quake.integer)
1225                                         {
1226                                                 color = particlepalette[67 + (rand()&3)];
1227                                                 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);
1228                                         }
1229                                         else
1230                                         {
1231                                                 dec = 16;
1232                                                 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);
1233                                         }
1234                                 }
1235                                 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1236                                 {
1237                                         if (cl_particles_quake.integer)
1238                                         {
1239                                                 dec = 6;
1240                                                 color = particlepalette[67 + (rand()&3)];
1241                                                 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);
1242                                         }
1243                                         else
1244                                         {
1245                                                 dec = 32;
1246                                                 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);
1247                                         }
1248                                 }
1249                         }
1250                         if (smoke)
1251                         {
1252                                 if (effectnameindex == EFFECT_TR_ROCKET)
1253                                 {
1254                                         if (cl_particles_quake.integer)
1255                                         {
1256                                                 r = rand()&3;
1257                                                 color = particlepalette[ramp3[r]];
1258                                                 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);
1259                                         }
1260                                         else
1261                                         {
1262                                                 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);
1263                                                 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);
1264                                         }
1265                                 }
1266                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1267                                 {
1268                                         if (cl_particles_quake.integer)
1269                                         {
1270                                                 r = 2 + (rand()%5);
1271                                                 color = particlepalette[ramp3[r]];
1272                                                 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);
1273                                         }
1274                                         else
1275                                         {
1276                                                 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);
1277                                         }
1278                                 }
1279                                 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1280                                 {
1281                                         if (cl_particles_quake.integer)
1282                                         {
1283                                                 dec = 6;
1284                                                 color = particlepalette[52 + (rand()&7)];
1285                                                 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);
1286                                                 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);
1287                                         }
1288                                         else if (gamemode == GAME_GOODVSBAD2)
1289                                         {
1290                                                 dec = 6;
1291                                                 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);
1292                                         }
1293                                         else
1294                                         {
1295                                                 color = particlepalette[20 + (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_KNIGHTSPIKE)
1300                                 {
1301                                         if (cl_particles_quake.integer)
1302                                         {
1303                                                 dec = 6;
1304                                                 color = particlepalette[230 + (rand()&7)];
1305                                                 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);
1306                                                 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);
1307                                         }
1308                                         else
1309                                         {
1310                                                 color = particlepalette[226 + (rand()&7)];
1311                                                 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);
1312                                         }
1313                                 }
1314                                 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1315                                 {
1316                                         if (cl_particles_quake.integer)
1317                                         {
1318                                                 color = particlepalette[152 + (rand()&3)];
1319                                                 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);
1320                                         }
1321                                         else if (gamemode == GAME_GOODVSBAD2)
1322                                         {
1323                                                 dec = 6;
1324                                                 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);
1325                                         }
1326                                         else if (gamemode == GAME_PRYDON)
1327                                         {
1328                                                 dec = 6;
1329                                                 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);
1330                                         }
1331                                         else
1332                                                 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);
1333                                 }
1334                                 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1335                                 {
1336                                         dec = 7;
1337                                         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);
1338                                 }
1339                                 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1340                                 {
1341                                         dec = 4;
1342                                         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);
1343                                 }
1344                                 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1345                                         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);
1346                         }
1347                         if (bubbles)
1348                         {
1349                                 if (effectnameindex == EFFECT_TR_ROCKET)
1350                                         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);
1351                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1352                                         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);
1353                         }
1354                         // advance to next time and position
1355                         dec *= qd;
1356                         len -= dec;
1357                         VectorMA (pos, dec, dir, pos);
1358                 }
1359                 if (ent)
1360                         ent->persistent.trail_time = len;
1361         }
1362         else
1363                 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1364 }
1365
1366 // this is also called on point effects with spawndlight = true and
1367 // spawnparticles = true
1368 // it is called CL_ParticleTrail because most code does not want to supply
1369 // these parameters, only trail handling does
1370 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])
1371 {
1372         qboolean found = false;
1373         if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1374         {
1375                 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1376                 return; // no such effect
1377         }
1378         if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1379         {
1380                 int effectinfoindex;
1381                 int supercontents;
1382                 int tex, staintex;
1383                 particleeffectinfo_t *info;
1384                 vec3_t center;
1385                 vec3_t traildir;
1386                 vec3_t trailpos;
1387                 vec3_t rvec;
1388                 vec_t traillen;
1389                 vec_t trailstep;
1390                 qboolean underwater;
1391                 qboolean immediatebloodstain;
1392                 particle_t *part;
1393                 float avgtint[4], tint[4], tintlerp;
1394                 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1395                 VectorLerp(originmins, 0.5, originmaxs, center);
1396                 supercontents = CL_PointSuperContents(center);
1397                 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1398                 VectorSubtract(originmaxs, originmins, traildir);
1399                 traillen = VectorLength(traildir);
1400                 VectorNormalize(traildir);
1401                 if(tintmins)
1402                 {
1403                         Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1404                 }
1405                 else
1406                 {
1407                         Vector4Set(avgtint, 1, 1, 1, 1);
1408                 }
1409                 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1410                 {
1411                         if (info->effectnameindex == effectnameindex)
1412                         {
1413                                 found = true;
1414                                 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1415                                         continue;
1416                                 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1417                                         continue;
1418
1419                                 // spawn a dlight if requested
1420                                 if (info->lightradiusstart > 0 && spawndlight)
1421                                 {
1422                                         matrix4x4_t tempmatrix;
1423                                         if (info->trailspacing > 0)
1424                                                 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1425                                         else
1426                                                 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1427                                         if (info->lighttime > 0 && info->lightradiusfade > 0)
1428                                         {
1429                                                 // light flash (explosion, etc)
1430                                                 // called when effect starts
1431                                                 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);
1432                                         }
1433                                         else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1434                                         {
1435                                                 // glowing entity
1436                                                 // called by CL_LinkNetworkEntity
1437                                                 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1438                                                 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1439                                                 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1440                                                 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1441                                                 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);
1442                                                 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1443                                         }
1444                                 }
1445
1446                                 if (!spawnparticles)
1447                                         continue;
1448
1449                                 // spawn particles
1450                                 tex = info->tex[0];
1451                                 if (info->tex[1] > info->tex[0])
1452                                 {
1453                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1454                                         tex = min(tex, info->tex[1] - 1);
1455                                 }
1456                                 if(info->staintex[0] < 0)
1457                                         staintex = info->staintex[0];
1458                                 else
1459                                 {
1460                                         staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1461                                         staintex = min(staintex, info->staintex[1] - 1);
1462                                 }
1463                                 if (info->particletype == pt_decal)
1464                                         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]);
1465                                 else if (info->orientation == PARTICLE_HBEAM)
1466                                         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);
1467                                 else
1468                                 {
1469                                         if (!cl_particles.integer)
1470                                                 continue;
1471                                         switch (info->particletype)
1472                                         {
1473                                         case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1474                                         case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1475                                         case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1476                                         case pt_blood: if (!cl_particles_blood.integer) continue;break;
1477                                         case pt_rain: if (!cl_particles_rain.integer) continue;break;
1478                                         case pt_snow: if (!cl_particles_snow.integer) continue;break;
1479                                         default: break;
1480                                         }
1481                                         VectorCopy(originmins, trailpos);
1482                                         if (info->trailspacing > 0)
1483                                         {
1484                                                 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount;
1485                                                 trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount);
1486                                                 immediatebloodstain = false;
1487                                         }
1488                                         else
1489                                         {
1490                                                 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1491                                                 trailstep = 0;
1492                                                 immediatebloodstain =
1493                                                         ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1494                                                         ||
1495                                                         ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1496                                         }
1497                                         info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1498                                         for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1499                                         {
1500                                                 if (info->tex[1] > info->tex[0])
1501                                                 {
1502                                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1503                                                         tex = min(tex, info->tex[1] - 1);
1504                                                 }
1505                                                 if (!trailstep)
1506                                                 {
1507                                                         trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1508                                                         trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1509                                                         trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1510                                                 }
1511                                                 if(tintmins)
1512                                                 {
1513                                                         tintlerp = lhrandom(0, 1);
1514                                                         Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1515                                                 }
1516                                                 VectorRandom(rvec);
1517                                                 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);
1518                                                 if (immediatebloodstain && part)
1519                                                 {
1520                                                         immediatebloodstain = false;
1521                                                         CL_ImmediateBloodStain(part);
1522                                                 }
1523                                                 if (trailstep)
1524                                                         VectorMA(trailpos, trailstep, traildir, trailpos);
1525                                         }
1526                                 }
1527                         }
1528                 }
1529         }
1530         if (!found)
1531                 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1532 }
1533
1534 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)
1535 {
1536         CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL);
1537 }
1538
1539 /*
1540 ===============
1541 CL_EntityParticles
1542 ===============
1543 */
1544 void CL_EntityParticles (const entity_t *ent)
1545 {
1546         int i;
1547         float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1548         static vec3_t avelocities[NUMVERTEXNORMALS];
1549         if (!cl_particles.integer) return;
1550         if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1551
1552         Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1553
1554         if (!avelocities[0][0])
1555                 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1556                         avelocities[0][i] = lhrandom(0, 2.55);
1557
1558         for (i = 0;i < NUMVERTEXNORMALS;i++)
1559         {
1560                 yaw = cl.time * avelocities[i][0];
1561                 pitch = cl.time * avelocities[i][1];
1562                 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1563                 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1564                 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1565                 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);
1566         }
1567 }
1568
1569
1570 void CL_ReadPointFile_f (void)
1571 {
1572         vec3_t org, leakorg;
1573         int r, c, s;
1574         char *pointfile = NULL, *pointfilepos, *t, tchar;
1575         char name[MAX_QPATH];
1576
1577         if (!cl.worldmodel)
1578                 return;
1579
1580         dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1581         pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1582         if (!pointfile)
1583         {
1584                 Con_Printf("Could not open %s\n", name);
1585                 return;
1586         }
1587
1588         Con_Printf("Reading %s...\n", name);
1589         VectorClear(leakorg);
1590         c = 0;
1591         s = 0;
1592         pointfilepos = pointfile;
1593         while (*pointfilepos)
1594         {
1595                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1596                         pointfilepos++;
1597                 if (!*pointfilepos)
1598                         break;
1599                 t = pointfilepos;
1600                 while (*t && *t != '\n' && *t != '\r')
1601                         t++;
1602                 tchar = *t;
1603                 *t = 0;
1604 #if _MSC_VER >= 1400
1605 #define sscanf sscanf_s
1606 #endif
1607                 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1608                 *t = tchar;
1609                 pointfilepos = t;
1610                 if (r != 3)
1611                         break;
1612                 if (c == 0)
1613                         VectorCopy(org, leakorg);
1614                 c++;
1615
1616                 if (cl.num_particles < cl.max_particles - 3)
1617                 {
1618                         s++;
1619                         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);
1620                 }
1621         }
1622         Mem_Free(pointfile);
1623         VectorCopy(leakorg, org);
1624         Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1625
1626         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);
1627         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);
1628         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);
1629 }
1630
1631 /*
1632 ===============
1633 CL_ParseParticleEffect
1634
1635 Parse an effect out of the server message
1636 ===============
1637 */
1638 void CL_ParseParticleEffect (void)
1639 {
1640         vec3_t org, dir;
1641         int i, count, msgcount, color;
1642
1643         MSG_ReadVector(org, cls.protocol);
1644         for (i=0 ; i<3 ; i++)
1645                 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1646         msgcount = MSG_ReadByte ();
1647         color = MSG_ReadByte ();
1648
1649         if (msgcount == 255)
1650                 count = 1024;
1651         else
1652                 count = msgcount;
1653
1654         CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1655 }
1656
1657 /*
1658 ===============
1659 CL_ParticleExplosion
1660
1661 ===============
1662 */
1663 void CL_ParticleExplosion (const vec3_t org)
1664 {
1665         int i;
1666         trace_t trace;
1667         //vec3_t v;
1668         //vec3_t v2;
1669         R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1670         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1671
1672         if (cl_particles_quake.integer)
1673         {
1674                 for (i = 0;i < 1024;i++)
1675                 {
1676                         int r, color;
1677                         r = rand()&3;
1678                         if (i & 1)
1679                         {
1680                                 color = particlepalette[ramp1[r]];
1681                                 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);
1682                         }
1683                         else
1684                         {
1685                                 color = particlepalette[ramp2[r]];
1686                                 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);
1687                         }
1688                 }
1689         }
1690         else
1691         {
1692                 i = CL_PointSuperContents(org);
1693                 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1694                 {
1695                         if (cl_particles.integer && cl_particles_bubbles.integer)
1696                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1697                                         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);
1698                 }
1699                 else
1700                 {
1701                         if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1702                         {
1703                                 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1704                                 {
1705                                         int k = 0;
1706                                         vec3_t v, v2;
1707                                         do
1708                                         {
1709                                                 VectorRandom(v2);
1710                                                 VectorMA(org, 128, v2, v);
1711                                                 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1712                                         }
1713                                         while (k < 16 && trace.fraction < 0.1f);
1714                                         VectorSubtract(trace.endpos, org, v2);
1715                                         VectorScale(v2, 2.0f, v2);
1716                                         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);
1717                                 }
1718                         }
1719                 }
1720         }
1721
1722         if (cl_particles_explosions_shell.integer)
1723                 R_NewExplosion(org);
1724 }
1725
1726 /*
1727 ===============
1728 CL_ParticleExplosion2
1729
1730 ===============
1731 */
1732 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1733 {
1734         int i, k;
1735         if (!cl_particles.integer) return;
1736
1737         for (i = 0;i < 512 * cl_particles_quality.value;i++)
1738         {
1739                 k = particlepalette[colorStart + (i % colorLength)];
1740                 if (cl_particles_quake.integer)
1741                         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);
1742                 else
1743                         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);
1744         }
1745 }
1746
1747 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1748 {
1749         vec3_t center;
1750         VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1751         if (cl_particles_sparks.integer)
1752         {
1753                 sparkcount *= cl_particles_quality.value;
1754                 while(sparkcount-- > 0)
1755                         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);
1756         }
1757 }
1758
1759 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1760 {
1761         vec3_t center;
1762         VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1763         if (cl_particles_smoke.integer)
1764         {
1765                 smokecount *= cl_particles_quality.value;
1766                 while(smokecount-- > 0)
1767                         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);
1768         }
1769 }
1770
1771 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)
1772 {
1773         vec3_t center;
1774         int k;
1775         if (!cl_particles.integer) return;
1776         VectorMAM(0.5f, mins, 0.5f, maxs, center);
1777
1778         count = (int)(count * cl_particles_quality.value);
1779         while (count--)
1780         {
1781                 k = particlepalette[colorbase + (rand()&3)];
1782                 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);
1783         }
1784 }
1785
1786 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1787 {
1788         int k;
1789         float minz, maxz, lifetime = 30;
1790         vec3_t org;
1791         if (!cl_particles.integer) return;
1792         if (dir[2] < 0) // falling
1793         {
1794                 minz = maxs[2] + dir[2] * 0.1;
1795                 maxz = maxs[2];
1796                 if (cl.worldmodel)
1797                         lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1798         }
1799         else // rising??
1800         {
1801                 minz = mins[2];
1802                 maxz = maxs[2] + dir[2] * 0.1;
1803                 if (cl.worldmodel)
1804                         lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1805         }
1806
1807         count = (int)(count * cl_particles_quality.value);
1808
1809         switch(type)
1810         {
1811         case 0:
1812                 if (!cl_particles_rain.integer) break;
1813                 count *= 4; // ick, this should be in the mod or maps?
1814
1815                 while(count--)
1816                 {
1817                         k = particlepalette[colorbase + (rand()&3)];
1818                         VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1819                         if (gamemode == GAME_GOODVSBAD2)
1820                                 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);
1821                         else
1822                                 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);
1823                 }
1824                 break;
1825         case 1:
1826                 if (!cl_particles_snow.integer) break;
1827                 while(count--)
1828                 {
1829                         k = particlepalette[colorbase + (rand()&3)];
1830                         VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1831                         if (gamemode == GAME_GOODVSBAD2)
1832                                 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);
1833                         else
1834                                 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);
1835                 }
1836                 break;
1837         default:
1838                 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1839         }
1840 }
1841
1842 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1843 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1844 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1845 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1846 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1847 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1848
1849 #define PARTICLETEXTURESIZE 64
1850 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1851
1852 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1853 {
1854         float dz, f, dot;
1855         vec3_t normal;
1856         dz = 1 - (dx*dx+dy*dy);
1857         if (dz > 0) // it does hit the sphere
1858         {
1859                 f = 0;
1860                 // back side
1861                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1862                 VectorNormalize(normal);
1863                 dot = DotProduct(normal, light);
1864                 if (dot > 0.5) // interior reflection
1865                         f += ((dot *  2) - 1);
1866                 else if (dot < -0.5) // exterior reflection
1867                         f += ((dot * -2) - 1);
1868                 // front side
1869                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1870                 VectorNormalize(normal);
1871                 dot = DotProduct(normal, light);
1872                 if (dot > 0.5) // interior reflection
1873                         f += ((dot *  2) - 1);
1874                 else if (dot < -0.5) // exterior reflection
1875                         f += ((dot * -2) - 1);
1876                 f *= 128;
1877                 f += 16; // just to give it a haze so you can see the outline
1878                 f = bound(0, f, 255);
1879                 return (unsigned char) f;
1880         }
1881         else
1882                 return 0;
1883 }
1884
1885 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1886 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1887 {
1888         *basex = (texnum % particlefontcols) * particlefontcellwidth;
1889         *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1890         *width = particlefontcellwidth;
1891         *height = particlefontcellheight;
1892 }
1893
1894 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1895 {
1896         int basex, basey, w, h, y;
1897         CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1898         if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1899                 Sys_Error("invalid particle texture size for autogenerating");
1900         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1901                 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1902 }
1903
1904 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1905 {
1906         int x, y;
1907         float cx, cy, dx, dy, f, iradius;
1908         unsigned char *d;
1909         cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1910         cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1911         iradius = 1.0f / radius;
1912         alpha *= (1.0f / 255.0f);
1913         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1914         {
1915                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1916                 {
1917                         dx = (x - cx);
1918                         dy = (y - cy);
1919                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1920                         if (f > 0)
1921                         {
1922                                 if (f > 1)
1923                                         f = 1;
1924                                 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1925                                 d[0] += (int)(f * (blue  - d[0]));
1926                                 d[1] += (int)(f * (green - d[1]));
1927                                 d[2] += (int)(f * (red   - d[2]));
1928                         }
1929                 }
1930         }
1931 }
1932
1933 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1934 {
1935         int i;
1936         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1937         {
1938                 data[0] = bound(minb, data[0], maxb);
1939                 data[1] = bound(ming, data[1], maxg);
1940                 data[2] = bound(minr, data[2], maxr);
1941         }
1942 }
1943
1944 void particletextureinvert(unsigned char *data)
1945 {
1946         int i;
1947         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1948         {
1949                 data[0] = 255 - data[0];
1950                 data[1] = 255 - data[1];
1951                 data[2] = 255 - data[2];
1952         }
1953 }
1954
1955 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1956 static void R_InitBloodTextures (unsigned char *particletexturedata)
1957 {
1958         int i, j, k, m;
1959         size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1960         unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1961
1962         // blood particles
1963         for (i = 0;i < 8;i++)
1964         {
1965                 memset(data, 255, datasize);
1966                 for (k = 0;k < 24;k++)
1967                         particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1968                 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1969                 particletextureinvert(data);
1970                 setuptex(tex_bloodparticle[i], data, particletexturedata);
1971         }
1972
1973         // blood decals
1974         for (i = 0;i < 8;i++)
1975         {
1976                 memset(data, 255, datasize);
1977                 m = 8;
1978                 for (j = 1;j < 10;j++)
1979                         for (k = min(j, m - 1);k < m;k++)
1980                                 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1981                 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1982                 particletextureinvert(data);
1983                 setuptex(tex_blooddecal[i], data, particletexturedata);
1984         }
1985
1986         Mem_Free(data);
1987 }
1988
1989 //uncomment this to make engine save out particle font to a tga file when run
1990 //#define DUMPPARTICLEFONT
1991
1992 static void R_InitParticleTexture (void)
1993 {
1994         int x, y, d, i, k, m;
1995         int basex, basey, w, h;
1996         float dx, dy, f, s1, t1, s2, t2;
1997         vec3_t light;
1998         char *buf;
1999         fs_offset_t filesize;
2000         char texturename[MAX_QPATH];
2001         skinframe_t *sf;
2002
2003         // a note: decals need to modulate (multiply) the background color to
2004         // properly darken it (stain), and they need to be able to alpha fade,
2005         // this is a very difficult challenge because it means fading to white
2006         // (no change to background) rather than black (darkening everything
2007         // behind the whole decal polygon), and to accomplish this the texture is
2008         // inverted (dark red blood on white background becomes brilliant cyan
2009         // and white on black background) so we can alpha fade it to black, then
2010         // we invert it again during the blendfunc to make it work...
2011
2012 #ifndef DUMPPARTICLEFONT
2013         decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2014         if (decalskinframe)
2015         {
2016                 particlefonttexture = decalskinframe->base;
2017                 // TODO maybe allow custom grid size?
2018                 particlefontwidth = image_width;
2019                 particlefontheight = image_height;
2020                 particlefontcellwidth = image_width / 8;
2021                 particlefontcellheight = image_height / 8;
2022                 particlefontcols = 8;
2023                 particlefontrows = 8;
2024         }
2025         else
2026 #endif
2027         {
2028                 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2029                 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2030                 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2031                 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2032                 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2033
2034                 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2035                 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2036                 particlefontcols = 8;
2037                 particlefontrows = 8;
2038
2039                 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2040
2041                 // smoke
2042                 for (i = 0;i < 8;i++)
2043                 {
2044                         memset(data, 255, datasize);
2045                         do
2046                         {
2047                                 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2048                                 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2049                                 m = 0;
2050                                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2051                                 {
2052                                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2053                                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2054                                         {
2055                                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2056                                                 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2057                                                 if (d > 0)
2058                                                         d = (int)(d * (1-(dx*dx+dy*dy)));
2059                                                 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2060                                                 d = bound(0, d, 255);
2061                                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2062                                                 if (m < d)
2063                                                         m = d;
2064                                         }
2065                                 }
2066                         }
2067                         while (m < 224);
2068                         setuptex(tex_smoke[i], data, particletexturedata);
2069                 }
2070
2071                 // rain splash
2072                 memset(data, 255, datasize);
2073                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2074                 {
2075                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2076                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2077                         {
2078                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2079                                 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2080                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2081                         }
2082                 }
2083                 setuptex(tex_rainsplash, data, particletexturedata);
2084
2085                 // normal particle
2086                 memset(data, 255, datasize);
2087                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2088                 {
2089                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2090                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2091                         {
2092                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2093                                 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2094                                 d = bound(0, d, 255);
2095                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2096                         }
2097                 }
2098                 setuptex(tex_particle, data, particletexturedata);
2099
2100                 // rain
2101                 memset(data, 255, datasize);
2102                 light[0] = 1;light[1] = 1;light[2] = 1;
2103                 VectorNormalize(light);
2104                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2105                 {
2106                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2107                         // stretch upper half of bubble by +50% and shrink lower half by -50%
2108                         // (this gives an elongated teardrop shape)
2109                         if (dy > 0.5f)
2110                                 dy = (dy - 0.5f) * 2.0f;
2111                         else
2112                                 dy = (dy - 0.5f) / 1.5f;
2113                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2114                         {
2115                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2116                                 // shrink bubble width to half
2117                                 dx *= 2.0f;
2118                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2119                         }
2120                 }
2121                 setuptex(tex_raindrop, data, particletexturedata);
2122
2123                 // bubble
2124                 memset(data, 255, datasize);
2125                 light[0] = 1;light[1] = 1;light[2] = 1;
2126                 VectorNormalize(light);
2127                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2128                 {
2129                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2130                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2131                         {
2132                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2133                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2134                         }
2135                 }
2136                 setuptex(tex_bubble, data, particletexturedata);
2137
2138                 // Blood particles and blood decals
2139                 R_InitBloodTextures (particletexturedata);
2140
2141                 // bullet decals
2142                 for (i = 0;i < 8;i++)
2143                 {
2144                         memset(data, 255, datasize);
2145                         for (k = 0;k < 12;k++)
2146                                 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2147                         for (k = 0;k < 3;k++)
2148                                 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2149                         //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2150                         particletextureinvert(data);
2151                         setuptex(tex_bulletdecal[i], data, particletexturedata);
2152                 }
2153
2154 #ifdef DUMPPARTICLEFONT
2155                 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2156 #endif
2157
2158                 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2159                 particlefonttexture = decalskinframe->base;
2160
2161                 Mem_Free(particletexturedata);
2162                 Mem_Free(data);
2163                 Mem_Free(noise1);
2164                 Mem_Free(noise2);
2165         }
2166         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2167         {
2168                 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2169                 particletexture[i].texture = particlefonttexture;
2170                 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2171                 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2172                 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2173                 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2174         }
2175
2176 #ifndef DUMPPARTICLEFONT
2177         particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2178         if (!particletexture[tex_beam].texture)
2179 #endif
2180         {
2181                 unsigned char noise3[64][64], data2[64][16][4];
2182                 // nexbeam
2183                 fractalnoise(&noise3[0][0], 64, 4);
2184                 m = 0;
2185                 for (y = 0;y < 64;y++)
2186                 {
2187                         dy = (y - 0.5f*64) / (64*0.5f-1);
2188                         for (x = 0;x < 16;x++)
2189                         {
2190                                 dx = (x - 0.5f*16) / (16*0.5f-2);
2191                                 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2192                                 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2193                                 data2[y][x][3] = 255;
2194                         }
2195                 }
2196
2197 #ifdef DUMPPARTICLEFONT
2198                 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2199 #endif
2200                 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2201         }
2202         particletexture[tex_beam].s1 = 0;
2203         particletexture[tex_beam].t1 = 0;
2204         particletexture[tex_beam].s2 = 1;
2205         particletexture[tex_beam].t2 = 1;
2206
2207         // now load an texcoord/texture override file
2208         buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2209         if(buf)
2210         {
2211                 const char *bufptr;
2212                 bufptr = buf;
2213                 for(;;)
2214                 {
2215                         if(!COM_ParseToken_Simple(&bufptr, true, false))
2216                                 break;
2217                         if(!strcmp(com_token, "\n"))
2218                                 continue; // empty line
2219                         i = atoi(com_token);
2220
2221                         texturename[0] = 0;
2222                         s1 = 0;
2223                         t1 = 0;
2224                         s2 = 1;
2225                         t2 = 1;
2226
2227                         if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2228                         {
2229                                 strlcpy(texturename, com_token, sizeof(texturename));
2230                                 s1 = atof(com_token);
2231                                 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2232                                 {
2233                                         texturename[0] = 0;
2234                                         t1 = atof(com_token);
2235                                         if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2236                                         {
2237                                                 s2 = atof(com_token);
2238                                                 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2239                                                 {
2240                                                         t2 = atof(com_token);
2241                                                         strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2242                                                         if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2243                                                                 strlcpy(texturename, com_token, sizeof(texturename));
2244                                                 }
2245                                         }
2246                                 }
2247                                 else
2248                                         s1 = 0;
2249                         }
2250                         if (!texturename[0])
2251                         {
2252                                 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2253                                 continue;
2254                         }
2255                         if (i < 0 || i >= MAX_PARTICLETEXTURES)
2256                         {
2257                                 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2258                                 continue;
2259                         }
2260                         sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true);
2261                         if(!sf)
2262                         {
2263                                 // R_SkinFrame_LoadExternal already complained
2264                                 continue;
2265                         }
2266                         particletexture[i].texture = sf->base;
2267                         particletexture[i].s1 = s1;
2268                         particletexture[i].t1 = t1;
2269                         particletexture[i].s2 = s2;
2270                         particletexture[i].t2 = t2;
2271                 }
2272                 Mem_Free(buf);
2273         }
2274 }
2275
2276 static void r_part_start(void)
2277 {
2278         int i;
2279         // generate particlepalette for convenience from the main one
2280         for (i = 0;i < 256;i++)
2281                 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2282         particletexturepool = R_AllocTexturePool();
2283         R_InitParticleTexture ();
2284         CL_Particles_LoadEffectInfo();
2285 }
2286
2287 static void r_part_shutdown(void)
2288 {
2289         R_FreeTexturePool(&particletexturepool);
2290 }
2291
2292 static void r_part_newmap(void)
2293 {
2294         if (decalskinframe)
2295                 R_SkinFrame_MarkUsed(decalskinframe);
2296         CL_Particles_LoadEffectInfo();
2297 }
2298
2299 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2300 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2301
2302 void R_Particles_Init (void)
2303 {
2304         int i;
2305         for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2306         {
2307                 particle_elements[i*6+0] = i*4+0;
2308                 particle_elements[i*6+1] = i*4+1;
2309                 particle_elements[i*6+2] = i*4+2;
2310                 particle_elements[i*6+3] = i*4+0;
2311                 particle_elements[i*6+4] = i*4+2;
2312                 particle_elements[i*6+5] = i*4+3;
2313         }
2314
2315         Cvar_RegisterVariable(&r_drawparticles);
2316         Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2317         Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2318         Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2319         Cvar_RegisterVariable(&r_drawdecals);
2320         Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2321         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2322 }
2323
2324 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2325 {
2326         int surfacelistindex;
2327         const decal_t *d;
2328         float *v3f, *t2f, *c4f;
2329         particletexture_t *tex;
2330         float right[3], up[3], size, ca;
2331         float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2332
2333         RSurf_ActiveWorldEntity();
2334
2335         r_refdef.stats.drawndecals += numsurfaces;
2336 //      R_Mesh_ResetTextureState();
2337         GL_DepthMask(false);
2338         GL_DepthRange(0, 1);
2339         GL_PolygonOffset(0, 0);
2340         GL_DepthTest(true);
2341         GL_CullFace(GL_NONE);
2342
2343         // generate all the vertices at once
2344         for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2345         {
2346                 d = cl.decals + surfacelist[surfacelistindex];
2347
2348                 // calculate color
2349                 c4f = particle_color4f + 16*surfacelistindex;
2350                 ca = d->alpha * alphascale;
2351                 // ensure alpha multiplier saturates properly
2352                 if (ca > 1.0f / 256.0f)
2353                         ca = 1.0f / 256.0f;     
2354                 if (r_refdef.fogenabled)
2355                         ca *= RSurf_FogVertex(d->org);
2356                 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2357                 Vector4Copy(c4f, c4f + 4);
2358                 Vector4Copy(c4f, c4f + 8);
2359                 Vector4Copy(c4f, c4f + 12);
2360
2361                 // calculate vertex positions
2362                 size = d->size * cl_particles_size.value;
2363                 VectorVectors(d->normal, right, up);
2364                 VectorScale(right, size, right);
2365                 VectorScale(up, size, up);
2366                 v3f = particle_vertex3f + 12*surfacelistindex;
2367                 v3f[ 0] = d->org[0] - right[0] - up[0];
2368                 v3f[ 1] = d->org[1] - right[1] - up[1];
2369                 v3f[ 2] = d->org[2] - right[2] - up[2];
2370                 v3f[ 3] = d->org[0] - right[0] + up[0];
2371                 v3f[ 4] = d->org[1] - right[1] + up[1];
2372                 v3f[ 5] = d->org[2] - right[2] + up[2];
2373                 v3f[ 6] = d->org[0] + right[0] + up[0];
2374                 v3f[ 7] = d->org[1] + right[1] + up[1];
2375                 v3f[ 8] = d->org[2] + right[2] + up[2];
2376                 v3f[ 9] = d->org[0] + right[0] - up[0];
2377                 v3f[10] = d->org[1] + right[1] - up[1];
2378                 v3f[11] = d->org[2] + right[2] - up[2];
2379
2380                 // calculate texcoords
2381                 tex = &particletexture[d->texnum];
2382                 t2f = particle_texcoord2f + 8*surfacelistindex;
2383                 t2f[0] = tex->s1;t2f[1] = tex->t2;
2384                 t2f[2] = tex->s1;t2f[3] = tex->t1;
2385                 t2f[4] = tex->s2;t2f[5] = tex->t1;
2386                 t2f[6] = tex->s2;t2f[7] = tex->t2;
2387         }
2388
2389         // now render the decals all at once
2390         // (this assumes they all use one particle font texture!)
2391         GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2392         R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false);
2393         R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2394         R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2395 }
2396
2397 void R_DrawDecals (void)
2398 {
2399         int i;
2400         int drawdecals = r_drawdecals.integer;
2401         decal_t *decal;
2402         float frametime;
2403         float decalfade;
2404         float drawdist2;
2405         int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2406
2407         frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2408         cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2409
2410         // LordHavoc: early out conditions
2411         if (!cl.num_decals)
2412                 return;
2413
2414         decalfade = frametime * 256 / cl_decals_fadetime.value;
2415         drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2416         drawdist2 = drawdist2*drawdist2;
2417
2418         for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2419         {
2420                 if (!decal->typeindex)
2421                         continue;
2422
2423                 if (killsequence - decal->decalsequence > 0)
2424                         goto killdecal;
2425
2426                 if (cl.time > decal->time2 + cl_decals_time.value)
2427                 {
2428                         decal->alpha -= decalfade;
2429                         if (decal->alpha <= 0)
2430                                 goto killdecal;
2431                 }
2432
2433                 if (decal->owner)
2434                 {
2435                         if (cl.entities[decal->owner].render.model == decal->ownermodel)
2436                         {
2437                                 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2438                                 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2439                         }
2440                         else
2441                                 goto killdecal;
2442                 }
2443
2444                 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2445                         continue;
2446
2447                 if (!drawdecals)
2448                         continue;
2449
2450                 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))
2451                         R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2452                 continue;
2453 killdecal:
2454                 decal->typeindex = 0;
2455                 if (cl.free_decal > i)
2456                         cl.free_decal = i;
2457         }
2458
2459         // reduce cl.num_decals if possible
2460         while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2461                 cl.num_decals--;
2462
2463         if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2464         {
2465                 decal_t *olddecals = cl.decals;
2466                 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2467                 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2468                 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2469                 Mem_Free(olddecals);
2470         }
2471
2472         r_refdef.stats.totaldecals = cl.num_decals;
2473 }
2474
2475 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2476 {
2477         int surfacelistindex;
2478         int batchstart, batchcount;
2479         const particle_t *p;
2480         pblend_t blendmode;
2481         rtexture_t *texture;
2482         float *v3f, *t2f, *c4f;
2483         particletexture_t *tex;
2484         float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2485 //      float ambient[3], diffuse[3], diffusenormal[3];
2486         float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2487         vec4_t colormultiplier;
2488         float minparticledist_start, minparticledist_end;
2489         qboolean dofade;
2490
2491         RSurf_ActiveWorldEntity();
2492
2493         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));
2494
2495         r_refdef.stats.particles += numsurfaces;
2496 //      R_Mesh_ResetTextureState();
2497         GL_DepthMask(false);
2498         GL_DepthRange(0, 1);
2499         GL_PolygonOffset(0, 0);
2500         GL_DepthTest(true);
2501         GL_CullFace(GL_NONE);
2502
2503         spintime = r_refdef.scene.time;
2504
2505         minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2506         minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2507         dofade = (minparticledist_start < minparticledist_end);
2508
2509         // first generate all the vertices at once
2510         for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2511         {
2512                 p = cl.particles + surfacelist[surfacelistindex];
2513
2514                 blendmode = (pblend_t)p->blendmode;
2515                 palpha = p->alpha;
2516                 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2517                         palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward)  - minparticledist_start) / (minparticledist_end - minparticledist_start));
2518                 alpha = palpha * colormultiplier[3];
2519                 // ensure alpha multiplier saturates properly
2520                 if (alpha > 1.0f)
2521                         alpha = 1.0f;
2522
2523                 switch (blendmode)
2524                 {
2525                 case PBLEND_INVALID:
2526                 case PBLEND_INVMOD:
2527                         // additive and modulate can just fade out in fog (this is correct)
2528                         if (r_refdef.fogenabled)
2529                                 alpha *= RSurf_FogVertex(p->org);
2530                         // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2531                         alpha *= 1.0f / 256.0f;
2532                         c4f[0] = p->color[0] * alpha;
2533                         c4f[1] = p->color[1] * alpha;
2534                         c4f[2] = p->color[2] * alpha;
2535                         c4f[3] = 0;
2536                         break;
2537                 case PBLEND_ADD:
2538                         // additive and modulate can just fade out in fog (this is correct)
2539                         if (r_refdef.fogenabled)
2540                                 alpha *= RSurf_FogVertex(p->org);
2541                         // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2542                         c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2543                         c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2544                         c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2545                         c4f[3] = 0;
2546                         break;
2547                 case PBLEND_ALPHA:
2548                         c4f[0] = p->color[0] * colormultiplier[0];
2549                         c4f[1] = p->color[1] * colormultiplier[1];
2550                         c4f[2] = p->color[2] * colormultiplier[2];
2551                         c4f[3] = alpha;
2552                         // note: lighting is not cheap!
2553                         if (particletype[p->typeindex].lighting)
2554                                 R_LightPoint(c4f, p->org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2555                         // mix in the fog color
2556                         if (r_refdef.fogenabled)
2557                         {
2558                                 fog = RSurf_FogVertex(p->org);
2559                                 ifog = 1 - fog;
2560                                 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2561                                 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2562                                 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2563                         }
2564                         // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2565                         VectorScale(c4f, alpha, c4f);
2566                         break;
2567                 }
2568                 // copy the color into the other three vertices
2569                 Vector4Copy(c4f, c4f + 4);
2570                 Vector4Copy(c4f, c4f + 8);
2571                 Vector4Copy(c4f, c4f + 12);
2572
2573                 size = p->size * cl_particles_size.value;
2574                 tex = &particletexture[p->texnum];
2575                 switch(p->orientation)
2576                 {
2577 //              case PARTICLE_INVALID:
2578                 case PARTICLE_BILLBOARD:
2579                         if (p->angle + p->spin)
2580                         {
2581                                 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2582                                 spinsin = sin(spinrad) * size;
2583                                 spincos = cos(spinrad) * size;
2584                                 spinm1 = -p->stretch * spincos;
2585                                 spinm2 = -spinsin;
2586                                 spinm3 = spinsin;
2587                                 spinm4 = -p->stretch * spincos;
2588                                 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2589                                 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2590                         }
2591                         else
2592                         {
2593                                 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2594                                 VectorScale(r_refdef.view.up, size, up);
2595                         }
2596
2597                         v3f[ 0] = p->org[0] - right[0] - up[0];
2598                         v3f[ 1] = p->org[1] - right[1] - up[1];
2599                         v3f[ 2] = p->org[2] - right[2] - up[2];
2600                         v3f[ 3] = p->org[0] - right[0] + up[0];
2601                         v3f[ 4] = p->org[1] - right[1] + up[1];
2602                         v3f[ 5] = p->org[2] - right[2] + up[2];
2603                         v3f[ 6] = p->org[0] + right[0] + up[0];
2604                         v3f[ 7] = p->org[1] + right[1] + up[1];
2605                         v3f[ 8] = p->org[2] + right[2] + up[2];
2606                         v3f[ 9] = p->org[0] + right[0] - up[0];
2607                         v3f[10] = p->org[1] + right[1] - up[1];
2608                         v3f[11] = p->org[2] + right[2] - up[2];
2609                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2610                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2611                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2612                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2613                         break;
2614                 case PARTICLE_ORIENTED_DOUBLESIDED:
2615                         VectorVectors(p->vel, baseright, baseup);
2616                         if (p->angle + p->spin)
2617                         {
2618                                 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2619                                 spinsin = sin(spinrad) * size;
2620                                 spincos = cos(spinrad) * size;
2621                                 spinm1 = p->stretch * spincos;
2622                                 spinm2 = -spinsin;
2623                                 spinm3 = spinsin;
2624                                 spinm4 = p->stretch * spincos;
2625                                 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2626                                 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2627                         }
2628                         else
2629                         {
2630                                 VectorScale(baseright, size * p->stretch, right);
2631                                 VectorScale(baseup, size, up);
2632                         }
2633                         v3f[ 0] = p->org[0] - right[0] - up[0];
2634                         v3f[ 1] = p->org[1] - right[1] - up[1];
2635                         v3f[ 2] = p->org[2] - right[2] - up[2];
2636                         v3f[ 3] = p->org[0] - right[0] + up[0];
2637                         v3f[ 4] = p->org[1] - right[1] + up[1];
2638                         v3f[ 5] = p->org[2] - right[2] + up[2];
2639                         v3f[ 6] = p->org[0] + right[0] + up[0];
2640                         v3f[ 7] = p->org[1] + right[1] + up[1];
2641                         v3f[ 8] = p->org[2] + right[2] + up[2];
2642                         v3f[ 9] = p->org[0] + right[0] - up[0];
2643                         v3f[10] = p->org[1] + right[1] - up[1];
2644                         v3f[11] = p->org[2] + right[2] - up[2];
2645                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2646                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2647                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2648                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2649                         break;
2650                 case PARTICLE_SPARK:
2651                         len = VectorLength(p->vel);
2652                         VectorNormalize2(p->vel, up);
2653                         lenfactor = p->stretch * 0.04 * len;
2654                         if(lenfactor < size * 0.5)
2655                                 lenfactor = size * 0.5;
2656                         VectorMA(p->org, -lenfactor, up, v);
2657                         VectorMA(p->org,  lenfactor, up, up2);
2658                         R_CalcBeam_Vertex3f(v3f, v, up2, size);
2659                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2660                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2661                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2662                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2663                         break;
2664                 case PARTICLE_VBEAM:
2665                         R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2666                         VectorSubtract(p->vel, p->org, up);
2667                         VectorNormalize(up);
2668                         v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2669                         v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2670                         t2f[0] = tex->s2;t2f[1] = v[0];
2671                         t2f[2] = tex->s1;t2f[3] = v[0];
2672                         t2f[4] = tex->s1;t2f[5] = v[1];
2673                         t2f[6] = tex->s2;t2f[7] = v[1];
2674                         break;
2675                 case PARTICLE_HBEAM:
2676                         R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2677                         VectorSubtract(p->vel, p->org, up);
2678                         VectorNormalize(up);
2679                         v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2680                         v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2681                         t2f[0] = v[0];t2f[1] = tex->t1;
2682                         t2f[2] = v[0];t2f[3] = tex->t2;
2683                         t2f[4] = v[1];t2f[5] = tex->t2;
2684                         t2f[6] = v[1];t2f[7] = tex->t1;
2685                         break;
2686                 }
2687         }
2688
2689         // now render batches of particles based on blendmode and texture
2690         blendmode = PBLEND_INVALID;
2691         texture = NULL;
2692         batchstart = 0;
2693         batchcount = 0;
2694         R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2695         for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2696         {
2697                 p = cl.particles + surfacelist[surfacelistindex];
2698
2699                 if (texture != particletexture[p->texnum].texture)
2700                 {
2701                         texture = particletexture[p->texnum].texture;
2702                         R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false);
2703                 }
2704
2705                 if (p->blendmode == PBLEND_INVMOD)
2706                 {
2707                         // inverse modulate blend - group these
2708                         GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2709                         // iterate until we find a change in settings
2710                         batchstart = surfacelistindex++;
2711                         for (;surfacelistindex < numsurfaces;surfacelistindex++)
2712                         {
2713                                 p = cl.particles + surfacelist[surfacelistindex];
2714                                 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2715                                         break;
2716                         }
2717                 }
2718                 else
2719                 {
2720                         // additive or alpha blend - group these
2721                         // (we can group these because we premultiplied the texture alpha)
2722                         GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2723                         // iterate until we find a change in settings
2724                         batchstart = surfacelistindex++;
2725                         for (;surfacelistindex < numsurfaces;surfacelistindex++)
2726                         {
2727                                 p = cl.particles + surfacelist[surfacelistindex];
2728                                 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2729                                         break;
2730                         }
2731                 }
2732
2733                 batchcount = surfacelistindex - batchstart;
2734                 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2735         }
2736 }
2737
2738 void R_DrawParticles (void)
2739 {
2740         int i, a;
2741         int drawparticles = r_drawparticles.integer;
2742         float minparticledist_start;
2743         particle_t *p;
2744         float gravity, frametime, f, dist, oldorg[3];
2745         float drawdist2;
2746         int hitent;
2747         trace_t trace;
2748         qboolean update;
2749
2750         frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2751         cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2752
2753         // LordHavoc: early out conditions
2754         if (!cl.num_particles)
2755                 return;
2756
2757         minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2758         gravity = frametime * cl.movevars_gravity;
2759         update = frametime > 0;
2760         drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2761         drawdist2 = drawdist2*drawdist2;
2762
2763         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2764         {
2765                 if (!p->typeindex)
2766                 {
2767                         if (cl.free_particle > i)
2768                                 cl.free_particle = i;
2769                         continue;
2770                 }
2771
2772                 if (update)
2773                 {
2774                         if (p->delayedspawn > cl.time)
2775                                 continue;
2776
2777                         p->size += p->sizeincrease * frametime;
2778                         p->alpha -= p->alphafade * frametime;
2779
2780                         if (p->alpha <= 0 || p->die <= cl.time)
2781                                 goto killparticle;
2782
2783                         if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2784                         {
2785                                 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2786                                 {
2787                                         if (p->typeindex == pt_blood)
2788                                                 p->size += frametime * 8;
2789                                         else
2790                                                 p->vel[2] -= p->gravity * gravity;
2791                                         f = 1.0f - min(p->liquidfriction * frametime, 1);
2792                                         VectorScale(p->vel, f, p->vel);
2793                                 }
2794                                 else
2795                                 {
2796                                         p->vel[2] -= p->gravity * gravity;
2797                                         if (p->airfriction)
2798                                         {
2799                                                 f = 1.0f - min(p->airfriction * frametime, 1);
2800                                                 VectorScale(p->vel, f, p->vel);
2801                                         }
2802                                 }
2803
2804                                 VectorCopy(p->org, oldorg);
2805                                 VectorMA(p->org, frametime, p->vel, p->org);
2806 //                              if (p->bounce && cl.time >= p->delayedcollisions)
2807                                 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2808                                 {
2809                                         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, false);
2810                                         // if the trace started in or hit something of SUPERCONTENTS_NODROP
2811                                         // or if the trace hit something flagged as NOIMPACT
2812                                         // then remove the particle
2813                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2814                                                 goto killparticle;
2815                                         VectorCopy(trace.endpos, p->org);
2816                                         // react if the particle hit something
2817                                         if (trace.fraction < 1)
2818                                         {
2819                                                 VectorCopy(trace.endpos, p->org);
2820
2821                                                 if (p->staintexnum >= 0)
2822                                                 {
2823                                                         // blood - splash on solid
2824                                                         if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2825                                                         {
2826                                                                 R_Stain(p->org, 16,
2827                                                                         p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2828                                                                         p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2829                                                                 if (cl_decals.integer)
2830                                                                 {
2831                                                                         // create a decal for the blood splat
2832                                                                         a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2833                                                                         CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2834                                                                 }
2835                                                         }
2836                                                 }
2837
2838                                                 if (p->typeindex == pt_blood)
2839                                                 {
2840                                                         // blood - splash on solid
2841                                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2842                                                                 goto killparticle;
2843                                                         if(p->staintexnum == -1) // staintex < -1 means no stains at all
2844                                                         {
2845                                                                 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)));
2846                                                                 if (cl_decals.integer)
2847                                                                 {
2848                                                                         // create a decal for the blood splat
2849                                                                         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);
2850                                                                 }
2851                                                         }
2852                                                         goto killparticle;
2853                                                 }
2854                                                 else if (p->bounce < 0)
2855                                                 {
2856                                                         // bounce -1 means remove on impact
2857                                                         goto killparticle;
2858                                                 }
2859                                                 else
2860                                                 {
2861                                                         // anything else - bounce off solid
2862                                                         dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2863                                                         VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2864                                                 }
2865                                         }
2866                                 }
2867
2868                                 if (VectorLength2(p->vel) < 0.03)
2869                                 {
2870                                         if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
2871                                                 goto killparticle;
2872                                         VectorClear(p->vel);
2873                                 }
2874                         }
2875
2876                         if (p->typeindex != pt_static)
2877                         {
2878                                 switch (p->typeindex)
2879                                 {
2880                                 case pt_entityparticle:
2881                                         // particle that removes itself after one rendered frame
2882                                         if (p->time2)
2883                                                 goto killparticle;
2884                                         else
2885                                                 p->time2 = 1;
2886                                         break;
2887                                 case pt_blood:
2888                                         a = CL_PointSuperContents(p->org);
2889                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2890                                                 goto killparticle;
2891                                         break;
2892                                 case pt_bubble:
2893                                         a = CL_PointSuperContents(p->org);
2894                                         if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2895                                                 goto killparticle;
2896                                         break;
2897                                 case pt_rain:
2898                                         a = CL_PointSuperContents(p->org);
2899                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2900                                                 goto killparticle;
2901                                         break;
2902                                 case pt_snow:
2903                                         if (cl.time > p->time2)
2904                                         {
2905                                                 // snow flutter
2906                                                 p->time2 = cl.time + (rand() & 3) * 0.1;
2907                                                 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2908                                                 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2909                                         }
2910                                         a = CL_PointSuperContents(p->org);
2911                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2912                                                 goto killparticle;
2913                                         break;
2914                                 default:
2915                                         break;
2916                                 }
2917                         }
2918                 }
2919                 else if (p->delayedspawn > cl.time)
2920                         continue;
2921                 if (!drawparticles)
2922                         continue;
2923                 // don't render particles too close to the view (they chew fillrate)
2924                 // also don't render particles behind the view (useless)
2925                 // further checks to cull to the frustum would be too slow here
2926                 switch(p->typeindex)
2927                 {
2928                 case pt_beam:
2929                         // beams have no culling
2930                         R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2931                         break;
2932                 default:
2933                         if(cl_particles_visculling.integer)
2934                                 if (!r_refdef.viewcache.world_novis)
2935                                         if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2936                                         {
2937                                                 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2938                                                 if(leaf)
2939                                                         if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2940                                                                 continue;
2941                                         }
2942                         // anything else just has to be in front of the viewer and visible at this distance
2943                         if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2944                                 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2945                         break;
2946                 }
2947
2948                 continue;
2949 killparticle:
2950                 p->typeindex = 0;
2951                 if (cl.free_particle > i)
2952                         cl.free_particle = i;
2953         }
2954
2955         // reduce cl.num_particles if possible
2956         while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2957                 cl.num_particles--;
2958
2959         if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2960         {
2961                 particle_t *oldparticles = cl.particles;
2962                 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2963                 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2964                 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2965                 Mem_Free(oldparticles);
2966         }
2967 }