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