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