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