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