]> icculus.org git repositories - divverent/darkplaces.git/blob - cl_particles.c
MacOSX g++ setjmp fix from div0
[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_TE_WIZSPIKE)
542                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
543         else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
544                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
545         else if (effectnameindex == EFFECT_TE_SPIKE)
546         {
547                 if (cl_particles_bulletimpacts.integer)
548                 {
549                         if (cl_particles_quake.integer)
550                         {
551                                 if (cl_particles_smoke.integer)
552                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
553                         }
554                         else
555                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
556                 }
557                 // bullet hole
558                 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
559                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
560         }
561         else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
562         {
563                 if (cl_particles_bulletimpacts.integer)
564                 {
565                         if (cl_particles_quake.integer)
566                         {
567                                 if (cl_particles_smoke.integer)
568                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
569                         }
570                         else
571                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
572                 }
573                 // bullet hole
574                 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
575                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
576                 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);
577         }
578         else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
579         {
580                 if (cl_particles_bulletimpacts.integer)
581                 {
582                         if (cl_particles_quake.integer)
583                         {
584                                 if (cl_particles_smoke.integer)
585                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
586                         }
587                         else
588                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
589                 }
590                 // bullet hole
591                 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
592                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
593         }
594         else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
595         {
596                 if (cl_particles_bulletimpacts.integer)
597                 {
598                         if (cl_particles_quake.integer)
599                         {
600                                 if (cl_particles_smoke.integer)
601                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
602                         }
603                         else
604                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
605                 }
606                 // bullet hole
607                 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
608                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
609                 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);
610         }
611         else if (effectnameindex == EFFECT_TE_BLOOD)
612         {
613                 if (!cl_particles_blood.integer)
614                         return;
615                 if (cl_particles_quake.integer)
616                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
617                 else
618                 {
619                         static double bloodaccumulator = 0;
620                         bloodaccumulator += count * 0.333 * cl_particles_quality.value;
621                         for (;bloodaccumulator > 0;bloodaccumulator--)
622                                 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);
623                 }
624         }
625         else if (effectnameindex == EFFECT_TE_SPARK)
626                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count, 0);
627         else if (effectnameindex == EFFECT_TE_PLASMABURN)
628         {
629                 // plasma scorch mark
630                 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
631                 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
632                 CL_AllocDlight(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
633         }
634         else if (effectnameindex == EFFECT_TE_GUNSHOT)
635         {
636                 if (cl_particles_bulletimpacts.integer)
637                 {
638                         if (cl_particles_quake.integer)
639                                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
640                         else
641                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
642                 }
643                 // bullet hole
644                 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
645                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
646         }
647         else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
648         {
649                 if (cl_particles_bulletimpacts.integer)
650                 {
651                         if (cl_particles_quake.integer)
652                                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
653                         else
654                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
655                 }
656                 // bullet hole
657                 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
658                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
659                 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);
660         }
661         else if (effectnameindex == EFFECT_TE_EXPLOSION)
662         {
663                 CL_ParticleExplosion(center);
664                 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);
665         }
666         else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
667         {
668                 CL_ParticleExplosion(center);
669                 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);
670         }
671         else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
672         {
673                 if (cl_particles_quake.integer)
674                 {
675                         int i;
676                         for (i = 0;i < 1024 * cl_particles_quality.value;i++)
677                         {
678                                 if (i & 1)
679                                         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);
680                                 else
681                                         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);
682                         }
683                 }
684                 else
685                         CL_ParticleExplosion(center);
686                 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);
687         }
688         else if (effectnameindex == EFFECT_TE_SMALLFLASH)
689                 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);
690         else if (effectnameindex == EFFECT_TE_FLAMEJET)
691         {
692                 count *= cl_particles_quality.value;
693                 while (count-- > 0)
694                         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);
695         }
696         else if (effectnameindex == EFFECT_TE_LAVASPLASH)
697         {
698                 float i, j, inc, vel;
699                 vec3_t dir, org;
700
701                 inc = 8 / cl_particles_quality.value;
702                 for (i = -128;i < 128;i += inc)
703                 {
704                         for (j = -128;j < 128;j += inc)
705                         {
706                                 dir[0] = j + lhrandom(0, inc);
707                                 dir[1] = i + lhrandom(0, inc);
708                                 dir[2] = 256;
709                                 org[0] = center[0] + dir[0];
710                                 org[1] = center[1] + dir[1];
711                                 org[2] = center[2] + lhrandom(0, 64);
712                                 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
713                                 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);
714                         }
715                 }
716         }
717         else if (effectnameindex == EFFECT_TE_TELEPORT)
718         {
719                 float i, j, k, inc, vel;
720                 vec3_t dir;
721
722                 inc = 4 / cl_particles_quality.value;
723                 for (i = -16;i < 16;i += inc)
724                 {
725                         for (j = -16;j < 16;j += inc)
726                         {
727                                 for (k = -24;k < 32;k += inc)
728                                 {
729                                         VectorSet(dir, i*8, j*8, k*8);
730                                         VectorNormalize(dir);
731                                         vel = lhrandom(50, 113);
732                                         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);
733                                 }
734                         }
735                 }
736                 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);
737         }
738         else if (effectnameindex == EFFECT_TE_TEI_G3)
739                 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);
740         else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
741         {
742                 if (cl_particles_smoke.integer)
743                 {
744                         count *= 0.25f * cl_particles_quality.value;
745                         while (count-- > 0)
746                                 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);
747                 }
748         }
749         else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
750         {
751                 CL_ParticleExplosion(center);
752                 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);
753         }
754         else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
755         {
756                 float f;
757                 if (cl_stainmaps.integer)
758                         R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
759                 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
760                 if (cl_particles_smoke.integer)
761                         for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
762                                 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);
763                 if (cl_particles_sparks.integer)
764                         for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
765                                 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);
766                 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);
767         }
768         else if (effectnameindex == EFFECT_EF_FLAME)
769         {
770                 count *= 300 * cl_particles_quality.value;
771                 while (count-- > 0)
772                         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);
773                 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);
774         }
775         else if (effectnameindex == EFFECT_EF_STARDUST)
776         {
777                 count *= 200 * cl_particles_quality.value;
778                 while (count-- > 0)
779                         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);
780                 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);
781         }
782         else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
783         {
784                 vec3_t dir, pos;
785                 float len, dec, qd;
786                 int smoke, blood, bubbles, r, color;
787
788                 if (effectnameindex == EFFECT_TR_ROCKET)
789                         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);
790                 else if (effectnameindex == EFFECT_TR_VORESPIKE)
791                 {
792                         if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
793                                 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);
794                         else
795                                 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);
796                 }
797                 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
798                         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);
799
800                 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
801                         return;
802
803                 VectorSubtract(originmaxs, originmins, dir);
804                 len = VectorNormalizeLength(dir);
805                 dec = -ent->persistent.trail_time;
806                 ent->persistent.trail_time += len;
807                 if (ent->persistent.trail_time < 0.01f)
808                         return;
809
810                 // if we skip out, leave it reset
811                 ent->persistent.trail_time = 0.0f;
812
813                 // advance into this frame to reach the first puff location
814                 VectorMA(originmins, dec, dir, pos);
815                 len -= dec;
816
817                 smoke = cl_particles.integer && cl_particles_smoke.integer;
818                 blood = cl_particles.integer && cl_particles_blood.integer;
819                 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
820                 qd = 1.0f / cl_particles_quality.value;
821
822                 while (len >= 0)
823                 {
824                         dec = 3;
825                         if (blood)
826                         {
827                                 if (effectnameindex == EFFECT_TR_BLOOD)
828                                 {
829                                         if (cl_particles_quake.integer)
830                                         {
831                                                 color = particlepalette[67 + (rand()&3)];
832                                                 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);
833                                         }
834                                         else
835                                         {
836                                                 dec = 16;
837                                                 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);
838                                         }
839                                 }
840                                 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
841                                 {
842                                         if (cl_particles_quake.integer)
843                                         {
844                                                 dec = 6;
845                                                 color = particlepalette[67 + (rand()&3)];
846                                                 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);
847                                         }
848                                         else
849                                         {
850                                                 dec = 32;
851                                                 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);
852                                         }
853                                 }
854                         }
855                         if (smoke)
856                         {
857                                 if (effectnameindex == EFFECT_TR_ROCKET)
858                                 {
859                                         if (cl_particles_quake.integer)
860                                         {
861                                                 r = rand()&3;
862                                                 color = particlepalette[ramp3[r]];
863                                                 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);
864                                         }
865                                         else
866                                         {
867                                                 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);
868                                                 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);
869                                         }
870                                 }
871                                 else if (effectnameindex == EFFECT_TR_GRENADE)
872                                 {
873                                         if (cl_particles_quake.integer)
874                                         {
875                                                 r = 2 + (rand()%5);
876                                                 color = particlepalette[ramp3[r]];
877                                                 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);
878                                         }
879                                         else
880                                         {
881                                                 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);
882                                         }
883                                 }
884                                 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
885                                 {
886                                         if (cl_particles_quake.integer)
887                                         {
888                                                 dec = 6;
889                                                 color = particlepalette[52 + (rand()&7)];
890                                                 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);
891                                                 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);
892                                         }
893                                         else if (gamemode == GAME_GOODVSBAD2)
894                                         {
895                                                 dec = 6;
896                                                 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);
897                                         }
898                                         else
899                                         {
900                                                 color = particlepalette[20 + (rand()&7)];
901                                                 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);
902                                         }
903                                 }
904                                 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
905                                 {
906                                         if (cl_particles_quake.integer)
907                                         {
908                                                 dec = 6;
909                                                 color = particlepalette[230 + (rand()&7)];
910                                                 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);
911                                                 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);
912                                         }
913                                         else
914                                         {
915                                                 color = particlepalette[226 + (rand()&7)];
916                                                 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);
917                                         }
918                                 }
919                                 else if (effectnameindex == EFFECT_TR_VORESPIKE)
920                                 {
921                                         if (cl_particles_quake.integer)
922                                         {
923                                                 color = particlepalette[152 + (rand()&3)];
924                                                 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);
925                                         }
926                                         else if (gamemode == GAME_GOODVSBAD2)
927                                         {
928                                                 dec = 6;
929                                                 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);
930                                         }
931                                         else if (gamemode == GAME_PRYDON)
932                                         {
933                                                 dec = 6;
934                                                 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);
935                                         }
936                                         else
937                                                 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);
938                                 }
939                                 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
940                                 {
941                                         dec = 7;
942                                         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);
943                                 }
944                                 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
945                                 {
946                                         dec = 4;
947                                         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);
948                                 }
949                                 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
950                                         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);
951                         }
952                         if (bubbles)
953                         {
954                                 if (effectnameindex == EFFECT_TR_ROCKET)
955                                         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);
956                                 else if (effectnameindex == EFFECT_TR_GRENADE)
957                                         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);
958                         }
959                         // advance to next time and position
960                         dec *= qd;
961                         len -= dec;
962                         VectorMA (pos, dec, dir, pos);
963                 }
964                 ent->persistent.trail_time = len;
965         }
966         else if (developer.integer >= 1)
967                 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
968 }
969
970 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)
971 {
972         vec3_t center;
973         qboolean found = false;
974         if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
975                 return; // invalid effect index
976         if (!particleeffectname[effectnameindex][0])
977                 return; // no such effect
978         VectorLerp(originmins, 0.5, originmaxs, center);
979         if (effectnameindex == EFFECT_SVC_PARTICLE)
980         {
981                 if (!cl_particles.integer)
982                         return;
983                 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
984                 if (pcount == 1024)
985                         CL_ParticleExplosion(center);
986                 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
987                         CL_ParticleEffect(EFFECT_TE_BLOOD, pcount / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
988                 else
989                 {
990                         float count = pcount * cl_particles_quality.value;
991                         for (;count > 0;count--)
992                         {
993                                 int k = particlepalette[palettecolor + (rand()&7)];
994                                 if (cl_particles_quake.integer)
995                                         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);
996                                 else if (gamemode == GAME_GOODVSBAD2)
997                                         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);
998                                 else
999                                         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);
1000                         }
1001                 }
1002         }
1003         else if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1004         {
1005                 int effectinfoindex;
1006                 int supercontents;
1007                 int tex;
1008                 particleeffectinfo_t *info;
1009                 vec3_t center;
1010                 vec3_t centervelocity;
1011                 vec3_t traildir;
1012                 vec3_t trailpos;
1013                 vec3_t rvec;
1014                 qboolean underwater;
1015                 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1016                 VectorLerp(originmins, 0.5, originmaxs, center);
1017                 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1018                 supercontents = CL_PointSuperContents(center);
1019                 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1020                 VectorSubtract(originmaxs, originmins, traildir);
1021                 VectorNormalize(traildir);
1022                 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1023                 {
1024                         if (info->effectnameindex == effectnameindex)
1025                         {
1026                                 found = true;
1027                                 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1028                                         continue;
1029                                 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1030                                         continue;
1031
1032                                 // spawn a dlight if requested
1033                                 if (info->lightradiusstart > 0)
1034                                 {
1035                                         matrix4x4_t tempmatrix;
1036                                         if (info->trailspacing > 0)
1037                                                 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1038                                         else
1039                                                 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1040                                         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);
1041                                 }
1042
1043                                 // spawn particles
1044                                 tex = info->tex[0];
1045                                 if (info->tex[1] > info->tex[0])
1046                                 {
1047                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1048                                         tex = min(tex, info->tex[1] - 1);
1049                                 }
1050                                 if (info->particletype == pt_decal)
1051                                         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]);
1052                                 else if (info->particletype == pt_beam)
1053                                         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);
1054                                 else
1055                                 {
1056                                         if (!cl_particles.integer)
1057                                                 continue;
1058                                         switch (info->particletype)
1059                                         {
1060                                         case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1061                                         case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1062                                         case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1063                                         case pt_blood: if (!cl_particles_blood.integer) continue;break;
1064                                         default: break;
1065                                         }
1066                                         info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1067                                         VectorCopy(originmins, trailpos);
1068                                         for (;info->particleaccumulator > 0;info->particleaccumulator--)
1069                                         {
1070                                                 if (info->tex[1] > info->tex[0])
1071                                                 {
1072                                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1073                                                         tex = min(tex, info->tex[1] - 1);
1074                                                 }
1075                                                 if (info->trailspacing <= 0)
1076                                                 {
1077                                                         trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1078                                                         trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1079                                                         trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1080                                                 }
1081                                                 VectorRandom(rvec);
1082                                                 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);
1083                                                 if (info->trailspacing > 0)
1084                                                         VectorMA(trailpos, info->trailspacing, traildir, trailpos);
1085                                         }
1086                                 }
1087                         }
1088                 }
1089         }
1090         if (!found)
1091                 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor);
1092 }
1093
1094 /*
1095 ===============
1096 CL_EntityParticles
1097 ===============
1098 */
1099 void CL_EntityParticles (const entity_t *ent)
1100 {
1101         int i;
1102         float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1103         static vec3_t avelocities[NUMVERTEXNORMALS];
1104         if (!cl_particles.integer) return;
1105
1106         Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1107
1108         if (!avelocities[0][0])
1109                 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1110                         avelocities[0][i] = lhrandom(0, 2.55);
1111
1112         for (i = 0;i < NUMVERTEXNORMALS;i++)
1113         {
1114                 yaw = cl.time * avelocities[i][0];
1115                 pitch = cl.time * avelocities[i][1];
1116                 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1117                 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1118                 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1119                 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);
1120         }
1121 }
1122
1123
1124 void CL_ReadPointFile_f (void)
1125 {
1126         vec3_t org, leakorg;
1127         int r, c, s;
1128         char *pointfile = NULL, *pointfilepos, *t, tchar;
1129         char name[MAX_OSPATH];
1130
1131         if (!cl.worldmodel)
1132                 return;
1133
1134         FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1135         strlcat (name, ".pts", sizeof (name));
1136         pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1137         if (!pointfile)
1138         {
1139                 Con_Printf("Could not open %s\n", name);
1140                 return;
1141         }
1142
1143         Con_Printf("Reading %s...\n", name);
1144         VectorClear(leakorg);
1145         c = 0;
1146         s = 0;
1147         pointfilepos = pointfile;
1148         while (*pointfilepos)
1149         {
1150                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1151                         pointfilepos++;
1152                 if (!*pointfilepos)
1153                         break;
1154                 t = pointfilepos;
1155                 while (*t && *t != '\n' && *t != '\r')
1156                         t++;
1157                 tchar = *t;
1158                 *t = 0;
1159                 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1160                 *t = tchar;
1161                 pointfilepos = t;
1162                 if (r != 3)
1163                         break;
1164                 if (c == 0)
1165                         VectorCopy(org, leakorg);
1166                 c++;
1167
1168                 if (cl.num_particles < cl.max_particles - 3)
1169                 {
1170                         s++;
1171                         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);
1172                 }
1173         }
1174         Mem_Free(pointfile);
1175         VectorCopy(leakorg, org);
1176         Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1177
1178         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);
1179         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);
1180         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);
1181 }
1182
1183 /*
1184 ===============
1185 CL_ParseParticleEffect
1186
1187 Parse an effect out of the server message
1188 ===============
1189 */
1190 void CL_ParseParticleEffect (void)
1191 {
1192         vec3_t org, dir;
1193         int i, count, msgcount, color;
1194
1195         MSG_ReadVector(org, cls.protocol);
1196         for (i=0 ; i<3 ; i++)
1197                 dir[i] = MSG_ReadChar ();
1198         msgcount = MSG_ReadByte ();
1199         color = MSG_ReadByte ();
1200
1201         if (msgcount == 255)
1202                 count = 1024;
1203         else
1204                 count = msgcount;
1205
1206         CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1207 }
1208
1209 /*
1210 ===============
1211 CL_ParticleExplosion
1212
1213 ===============
1214 */
1215 void CL_ParticleExplosion (const vec3_t org)
1216 {
1217         int i;
1218         trace_t trace;
1219         //vec3_t v;
1220         //vec3_t v2;
1221         if (cl_stainmaps.integer)
1222                 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1223         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1224
1225         if (cl_particles_quake.integer)
1226         {
1227                 for (i = 0;i < 1024;i++)
1228                 {
1229                         int r, color;
1230                         r = rand()&3;
1231                         if (i & 1)
1232                         {
1233                                 color = particlepalette[ramp1[r]];
1234                                 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);
1235                         }
1236                         else
1237                         {
1238                                 color = particlepalette[ramp2[r]];
1239                                 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);
1240                         }
1241                 }
1242         }
1243         else
1244         {
1245                 i = CL_PointSuperContents(org);
1246                 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1247                 {
1248                         if (cl_particles.integer && cl_particles_bubbles.integer)
1249                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1250                                         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);
1251                 }
1252                 else
1253                 {
1254                         // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1255                         // smoke puff
1256                         if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1257                         {
1258                                 for (i = 0;i < 32;i++)
1259                                 {
1260                                         int k;
1261                                         vec3_t v, v2;
1262                                         for (k = 0;k < 16;k++)
1263                                         {
1264                                                 v[0] = org[0] + lhrandom(-48, 48);
1265                                                 v[1] = org[1] + lhrandom(-48, 48);
1266                                                 v[2] = org[2] + lhrandom(-48, 48);
1267                                                 trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
1268                                                 if (trace.fraction >= 0.1)
1269                                                         break;
1270                                         }
1271                                         VectorSubtract(trace.endpos, org, v2);
1272                                         VectorScale(v2, 2.0f, v2);
1273                                         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);
1274                                 }
1275                         }
1276
1277                         if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1278                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1279                                         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);
1280                 }
1281         }
1282
1283         if (cl_particles_explosions_shell.integer)
1284                 R_NewExplosion(org);
1285 }
1286
1287 /*
1288 ===============
1289 CL_ParticleExplosion2
1290
1291 ===============
1292 */
1293 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1294 {
1295         int i, k;
1296         if (!cl_particles.integer) return;
1297
1298         for (i = 0;i < 512 * cl_particles_quality.value;i++)
1299         {
1300                 k = particlepalette[colorStart + (i % colorLength)];
1301                 if (cl_particles_quake.integer)
1302                         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);
1303                 else
1304                         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);
1305         }
1306 }
1307
1308 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1309 {
1310         if (cl_particles_sparks.integer)
1311         {
1312                 sparkcount *= cl_particles_quality.value;
1313                 while(sparkcount-- > 0)
1314                         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);
1315         }
1316         if (cl_particles_smoke.integer)
1317         {
1318                 smokecount *= cl_particles_quality.value;
1319                 while(smokecount-- > 0)
1320                         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);
1321         }
1322 }
1323
1324 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)
1325 {
1326         int k;
1327         if (!cl_particles.integer) return;
1328
1329         count = (int)(count * cl_particles_quality.value);
1330         while (count--)
1331         {
1332                 k = particlepalette[colorbase + (rand()&3)];
1333                 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);
1334         }
1335 }
1336
1337 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1338 {
1339         int k;
1340         float z, minz, maxz;
1341         particle_t *p;
1342         if (!cl_particles.integer) return;
1343         if (dir[2] < 0) // falling
1344                 z = maxs[2];
1345         else // rising??
1346                 z = mins[2];
1347
1348         minz = z - fabs(dir[2]) * 0.1;
1349         maxz = z + fabs(dir[2]) * 0.1;
1350         minz = bound(mins[2], minz, maxs[2]);
1351         maxz = bound(mins[2], maxz, maxs[2]);
1352
1353         count = (int)(count * cl_particles_quality.value);
1354
1355         switch(type)
1356         {
1357         case 0:
1358                 count *= 4; // ick, this should be in the mod or maps?
1359
1360                 while(count--)
1361                 {
1362                         k = particlepalette[colorbase + (rand()&3)];
1363                         if (gamemode == GAME_GOODVSBAD2)
1364                                 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);
1365                         else
1366                                 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);
1367                 }
1368                 break;
1369         case 1:
1370                 while(count--)
1371                 {
1372                         k = particlepalette[colorbase + (rand()&3)];
1373                         if (gamemode == GAME_GOODVSBAD2)
1374                                 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);
1375                         else
1376                                 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);
1377                         if (p)
1378                                 VectorCopy(p->vel, p->relativedirection);
1379                 }
1380                 break;
1381         default:
1382                 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1383         }
1384 }
1385
1386 /*
1387 ===============
1388 CL_MoveParticles
1389 ===============
1390 */
1391 void CL_MoveParticles (void)
1392 {
1393         particle_t *p;
1394         int i, maxparticle, j, a, content;
1395         float gravity, dvel, bloodwaterfade, frametime, f, dist, org[3], oldorg[3];
1396         int hitent;
1397         trace_t trace;
1398
1399         // LordHavoc: early out condition
1400         if (!cl.num_particles)
1401         {
1402                 cl.free_particle = 0;
1403                 return;
1404         }
1405
1406         frametime = cl.time - cl.oldtime;
1407         gravity = frametime * sv_gravity.value;
1408         dvel = 1+4*frametime;
1409         bloodwaterfade = max(cl_particles_blood_alpha.value, 0.01f) * frametime * 128.0f;
1410
1411         maxparticle = -1;
1412         j = 0;
1413         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1414         {
1415                 if (!p->type)
1416                         continue;
1417                 maxparticle = i;
1418                 content = 0;
1419
1420                 p->alpha -= p->alphafade * frametime;
1421
1422                 if (p->alpha <= 0)
1423                 {
1424                         p->type = NULL;
1425                         continue;
1426                 }
1427
1428                 if (p->type->orientation != PARTICLE_BEAM)
1429                 {
1430                         VectorCopy(p->org, oldorg);
1431                         VectorMA(p->org, frametime, p->vel, p->org);
1432                         VectorCopy(p->org, org);
1433                         if (p->bounce)
1434                         {
1435                                 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);
1436                                 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1437                                 // or if the trace hit something flagged as NOIMPACT
1438                                 // then remove the particle
1439                                 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1440                                 {
1441                                         p->type = NULL;
1442                                         continue;
1443                                 }
1444                                 // react if the particle hit something
1445                                 if (trace.fraction < 1)
1446                                 {
1447                                         VectorCopy(trace.endpos, p->org);
1448                                         if (p->type == particletype + pt_rain)
1449                                         {
1450                                                 // raindrop - splash on solid/water/slime/lava
1451                                                 int count;
1452                                                 // convert from a raindrop particle to a rainsplash decal
1453                                                 VectorCopy(trace.plane.normal, p->vel);
1454                                                 VectorAdd(p->org, p->vel, p->org);
1455                                                 p->type = particletype + pt_raindecal;
1456                                                 p->texnum = tex_rainsplash[0];
1457                                                 p->time2 = cl.time;
1458                                                 p->alphafade = p->alpha / 0.4;
1459                                                 p->bounce = 0;
1460                                                 p->friction = 0;
1461                                                 p->gravity = 0;
1462                                                 p->size = 8.0;
1463                                                 count = rand() & 3;
1464                                                 while(count--)
1465                                                         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);
1466                                         }
1467                                         else if (p->type == particletype + pt_blood)
1468                                         {
1469                                                 // blood - splash on solid
1470                                                 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1471                                                 {
1472                                                         p->type = NULL;
1473                                                         continue;
1474                                                 }
1475                                                 if (!cl_decals.integer)
1476                                                 {
1477                                                         p->type = NULL;
1478                                                         continue;
1479                                                 }
1480                                                 // convert from a blood particle to a blood decal
1481                                                 VectorCopy(trace.plane.normal, p->vel);
1482                                                 VectorAdd(p->org, p->vel, p->org);
1483                                                 if (cl_stainmaps.integer)
1484                                                         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)));
1485
1486                                                 p->type = particletype + pt_decal;
1487                                                 p->texnum = tex_blooddecal[rand()&7];
1488                                                 p->owner = hitent;
1489                                                 p->ownermodel = cl.entities[hitent].render.model;
1490                                                 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1491                                                 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1492                                                 p->time2 = cl.time;
1493                                                 p->alphafade = 0;
1494                                                 p->bounce = 0;
1495                                                 p->friction = 0;
1496                                                 p->gravity = 0;
1497                                                 p->size *= 2.0f;
1498                                         }
1499                                         else if (p->bounce < 0)
1500                                         {
1501                                                 // bounce -1 means remove on impact
1502                                                 p->type = NULL;
1503                                                 continue;
1504                                         }
1505                                         else
1506                                         {
1507                                                 // anything else - bounce off solid
1508                                                 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1509                                                 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1510                                                 if (DotProduct(p->vel, p->vel) < 0.03)
1511                                                         VectorClear(p->vel);
1512                                         }
1513                                 }
1514                         }
1515                         p->vel[2] -= p->gravity * gravity;
1516
1517                         if (p->friction)
1518                         {
1519                                 f = p->friction * frametime;
1520                                 if (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1521                                         f *= 4;
1522                                 f = 1.0f - f;
1523                                 VectorScale(p->vel, f, p->vel);
1524                         }
1525                 }
1526
1527                 if (p->type != particletype + pt_static)
1528                 {
1529                         switch (p->type - particletype)
1530                         {
1531                         case pt_entityparticle:
1532                                 // particle that removes itself after one rendered frame
1533                                 if (p->time2)
1534                                         p->type = NULL;
1535                                 else
1536                                         p->time2 = 1;
1537                                 break;
1538                         case pt_blood:
1539                                 a = CL_PointSuperContents(p->org);
1540                                 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1541                                 {
1542                                         p->size += frametime * 8;
1543                                         //p->alpha -= bloodwaterfade;
1544                                 }
1545                                 else
1546                                         p->vel[2] -= gravity;
1547                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1548                                         p->type = NULL;
1549                                 break;
1550                         case pt_bubble:
1551                                 a = CL_PointSuperContents(p->org);
1552                                 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1553                                 {
1554                                         p->type = NULL;
1555                                         break;
1556                                 }
1557                                 break;
1558                         case pt_rain:
1559                                 a = CL_PointSuperContents(p->org);
1560                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1561                                         p->type = NULL;
1562                                 break;
1563                         case pt_snow:
1564                                 if (cl.time > p->time2)
1565                                 {
1566                                         // snow flutter
1567                                         p->time2 = cl.time + (rand() & 3) * 0.1;
1568                                         p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1569                                         p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1570                                         //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1571                                 }
1572                                 a = CL_PointSuperContents(p->org);
1573                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1574                                         p->type = NULL;
1575                                 break;
1576                         case pt_smoke:
1577                                 //p->size += frametime * 15;
1578                                 break;
1579                         case pt_decal:
1580                                 // FIXME: this has fairly wacky handling of alpha
1581                                 p->alphafade = cl.time > (p->time2 + cl_decals_time.value) ? (255 / cl_decals_fadetime.value) : 0;
1582                                 if (cl.entities[p->owner].render.model == p->ownermodel)
1583                                 {
1584                                         Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1585                                         Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1586                                 }
1587                                 else
1588                                         p->type = NULL;
1589                                 break;
1590                         case pt_raindecal:
1591                                 a = (int)max(0, (cl.time - p->time2) * 40);
1592                                 if (a < 16)
1593                                         p->texnum = tex_rainsplash[a];
1594                                 else
1595                                         p->type = NULL;
1596                                 break;
1597                         default:
1598                                 break;
1599                         }
1600                 }
1601         }
1602         cl.num_particles = maxparticle + 1;
1603         cl.free_particle = 0;
1604 }
1605
1606 #define MAX_PARTICLETEXTURES 64
1607 // particletexture_t is a rectangle in the particlefonttexture
1608 typedef struct particletexture_s
1609 {
1610         rtexture_t *texture;
1611         float s1, t1, s2, t2;
1612 }
1613 particletexture_t;
1614
1615 static rtexturepool_t *particletexturepool;
1616 static rtexture_t *particlefonttexture;
1617 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1618
1619 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1620
1621 #define PARTICLETEXTURESIZE 64
1622 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1623
1624 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1625 {
1626         float dz, f, dot;
1627         vec3_t normal;
1628         dz = 1 - (dx*dx+dy*dy);
1629         if (dz > 0) // it does hit the sphere
1630         {
1631                 f = 0;
1632                 // back side
1633                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1634                 VectorNormalize(normal);
1635                 dot = DotProduct(normal, light);
1636                 if (dot > 0.5) // interior reflection
1637                         f += ((dot *  2) - 1);
1638                 else if (dot < -0.5) // exterior reflection
1639                         f += ((dot * -2) - 1);
1640                 // front side
1641                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1642                 VectorNormalize(normal);
1643                 dot = DotProduct(normal, light);
1644                 if (dot > 0.5) // interior reflection
1645                         f += ((dot *  2) - 1);
1646                 else if (dot < -0.5) // exterior reflection
1647                         f += ((dot * -2) - 1);
1648                 f *= 128;
1649                 f += 16; // just to give it a haze so you can see the outline
1650                 f = bound(0, f, 255);
1651                 return (unsigned char) f;
1652         }
1653         else
1654                 return 0;
1655 }
1656
1657 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1658 {
1659         int basex, basey, y;
1660         basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1661         basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1662         particletexture[texnum].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1663         particletexture[texnum].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1664         particletexture[texnum].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1665         particletexture[texnum].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1666         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1667                 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1668 }
1669
1670 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1671 {
1672         int x, y;
1673         float cx, cy, dx, dy, f, iradius;
1674         unsigned char *d;
1675         cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1676         cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1677         iradius = 1.0f / radius;
1678         alpha *= (1.0f / 255.0f);
1679         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1680         {
1681                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1682                 {
1683                         dx = (x - cx);
1684                         dy = (y - cy);
1685                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1686                         if (f > 0)
1687                         {
1688                                 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1689                                 d[0] += (int)(f * (red   - d[0]));
1690                                 d[1] += (int)(f * (green - d[1]));
1691                                 d[2] += (int)(f * (blue  - d[2]));
1692                         }
1693                 }
1694         }
1695 }
1696
1697 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1698 {
1699         int i;
1700         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1701         {
1702                 data[0] = bound(minr, data[0], maxr);
1703                 data[1] = bound(ming, data[1], maxg);
1704                 data[2] = bound(minb, data[2], maxb);
1705         }
1706 }
1707
1708 void particletextureinvert(unsigned char *data)
1709 {
1710         int i;
1711         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1712         {
1713                 data[0] = 255 - data[0];
1714                 data[1] = 255 - data[1];
1715                 data[2] = 255 - data[2];
1716         }
1717 }
1718
1719 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1720 static void R_InitBloodTextures (unsigned char *particletexturedata)
1721 {
1722         int i, j, k, m;
1723         unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1724
1725         // blood particles
1726         for (i = 0;i < 8;i++)
1727         {
1728                 memset(&data[0][0][0], 255, sizeof(data));
1729                 for (k = 0;k < 24;k++)
1730                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1731                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1732                 particletextureinvert(&data[0][0][0]);
1733                 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1734         }
1735
1736         // blood decals
1737         for (i = 0;i < 8;i++)
1738         {
1739                 memset(&data[0][0][0], 255, sizeof(data));
1740                 m = 8;
1741                 for (j = 1;j < 10;j++)
1742                         for (k = min(j, m - 1);k < m;k++)
1743                                 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1744                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1745                 particletextureinvert(&data[0][0][0]);
1746                 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1747         }
1748
1749 }
1750
1751 static void R_InitParticleTexture (void)
1752 {
1753         int x, y, d, i, k, m;
1754         float dx, dy, radius, f, f2;
1755         unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4], noise3[64][64], data2[64][16][4];
1756         vec3_t light;
1757         unsigned char *particletexturedata;
1758
1759         // a note: decals need to modulate (multiply) the background color to
1760         // properly darken it (stain), and they need to be able to alpha fade,
1761         // this is a very difficult challenge because it means fading to white
1762         // (no change to background) rather than black (darkening everything
1763         // behind the whole decal polygon), and to accomplish this the texture is
1764         // inverted (dark red blood on white background becomes brilliant cyan
1765         // and white on black background) so we can alpha fade it to black, then
1766         // we invert it again during the blendfunc to make it work...
1767
1768         particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1769         memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1770
1771         // smoke
1772         for (i = 0;i < 8;i++)
1773         {
1774                 memset(&data[0][0][0], 255, sizeof(data));
1775                 do
1776                 {
1777                         unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1778
1779                         fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1780                         fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1781                         m = 0;
1782                         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1783                         {
1784                                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1785                                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1786                                 {
1787                                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1788                                         d = (noise2[y][x] - 128) * 3 + 192;
1789                                         if (d > 0)
1790                                                 d = (int)(d * (1-(dx*dx+dy*dy)));
1791                                         d = (d * noise1[y][x]) >> 7;
1792                                         d = bound(0, d, 255);
1793                                         data[y][x][3] = (unsigned char) d;
1794                                         if (m < d)
1795                                                 m = d;
1796                                 }
1797                         }
1798                 }
1799                 while (m < 224);
1800                 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1801         }
1802
1803         // rain splash
1804         for (i = 0;i < 16;i++)
1805         {
1806                 memset(&data[0][0][0], 255, sizeof(data));
1807                 radius = i * 3.0f / 4.0f / 16.0f;
1808                 f2 = 255.0f * ((15.0f - i) / 15.0f);
1809                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1810                 {
1811                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1812                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1813                         {
1814                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1815                                 f = f2 * (1.0 - 4.0f * fabs(radius - sqrt(dx*dx+dy*dy)));
1816                                 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1817                         }
1818                 }
1819                 setuptex(tex_rainsplash[i], &data[0][0][0], particletexturedata);
1820         }
1821
1822         // normal particle
1823         memset(&data[0][0][0], 255, sizeof(data));
1824         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1825         {
1826                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1827                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1828                 {
1829                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1830                         d = (int)(256 * (1 - (dx*dx+dy*dy)));
1831                         d = bound(0, d, 255);
1832                         data[y][x][3] = (unsigned char) d;
1833                 }
1834         }
1835         setuptex(tex_particle, &data[0][0][0], particletexturedata);
1836
1837         // rain
1838         memset(&data[0][0][0], 255, sizeof(data));
1839         light[0] = 1;light[1] = 1;light[2] = 1;
1840         VectorNormalize(light);
1841         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1842         {
1843                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1844                 // stretch upper half of bubble by +50% and shrink lower half by -50%
1845                 // (this gives an elongated teardrop shape)
1846                 if (dy > 0.5f)
1847                         dy = (dy - 0.5f) * 2.0f;
1848                 else
1849                         dy = (dy - 0.5f) / 1.5f;
1850                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1851                 {
1852                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1853                         // shrink bubble width to half
1854                         dx *= 2.0f;
1855                         data[y][x][3] = shadebubble(dx, dy, light);
1856                 }
1857         }
1858         setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1859
1860         // bubble
1861         memset(&data[0][0][0], 255, sizeof(data));
1862         light[0] = 1;light[1] = 1;light[2] = 1;
1863         VectorNormalize(light);
1864         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1865         {
1866                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1867                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1868                 {
1869                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1870                         data[y][x][3] = shadebubble(dx, dy, light);
1871                 }
1872         }
1873         setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1874
1875         // Blood particles and blood decals
1876         R_InitBloodTextures (particletexturedata);
1877
1878         // bullet decals
1879         for (i = 0;i < 8;i++)
1880         {
1881                 memset(&data[0][0][0], 255, sizeof(data));
1882                 for (k = 0;k < 12;k++)
1883                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1884                 for (k = 0;k < 3;k++)
1885                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1886                 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1887                 particletextureinvert(&data[0][0][0]);
1888                 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1889         }
1890
1891 #if 0
1892         Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1893 #endif
1894
1895         particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1896         if (!particlefonttexture)
1897                 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1898         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1899                 particletexture[i].texture = particlefonttexture;
1900
1901         // nexbeam
1902         fractalnoise(&noise3[0][0], 64, 4);
1903         m = 0;
1904         for (y = 0;y < 64;y++)
1905         {
1906                 dy = (y - 0.5f*64) / (64*0.5f-1);
1907                 for (x = 0;x < 16;x++)
1908                 {
1909                         dx = (x - 0.5f*16) / (16*0.5f-2);
1910                         d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1911                         data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1912                         data2[y][x][3] = 255;
1913                 }
1914         }
1915
1916 #if 0
1917         Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1918 #endif
1919
1920         particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1921         if (!particletexture[tex_beam].texture)
1922                 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1923         particletexture[tex_beam].s1 = 0;
1924         particletexture[tex_beam].t1 = 0;
1925         particletexture[tex_beam].s2 = 1;
1926         particletexture[tex_beam].t2 = 1;
1927         Mem_Free(particletexturedata);
1928 }
1929
1930 static void r_part_start(void)
1931 {
1932         particletexturepool = R_AllocTexturePool();
1933         R_InitParticleTexture ();
1934         CL_Particles_LoadEffectInfo();
1935 }
1936
1937 static void r_part_shutdown(void)
1938 {
1939         R_FreeTexturePool(&particletexturepool);
1940 }
1941
1942 static void r_part_newmap(void)
1943 {
1944 }
1945
1946 void R_Particles_Init (void)
1947 {
1948         Cvar_RegisterVariable(&r_drawparticles);
1949         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1950 }
1951
1952 float particle_vertex3f[12], particle_texcoord2f[8];
1953
1954 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight)
1955 {
1956         const particle_t *p = cl.particles + surfacenumber;
1957         rmeshstate_t m;
1958         pblend_t blendmode;
1959         float org[3], up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
1960         particletexture_t *tex;
1961
1962         VectorCopy(p->org, org);
1963
1964         blendmode = p->type->blendmode;
1965         tex = &particletexture[p->texnum];
1966         cr = p->color[0] * (1.0f / 255.0f);
1967         cg = p->color[1] * (1.0f / 255.0f);
1968         cb = p->color[2] * (1.0f / 255.0f);
1969         ca = p->alpha * (1.0f / 255.0f);
1970         if (blendmode == PBLEND_MOD)
1971         {
1972                 cr *= ca;
1973                 cg *= ca;
1974                 cb *= ca;
1975                 cr = min(cr, 1);
1976                 cg = min(cg, 1);
1977                 cb = min(cb, 1);
1978                 ca = 1;
1979         }
1980         ca /= cl_particles_quality.value;
1981         if (p->type->lighting)
1982         {
1983                 float ambient[3], diffuse[3], diffusenormal[3];
1984                 R_CompleteLightPoint(ambient, diffuse, diffusenormal, org, true);
1985                 cr *= (ambient[0] + 0.5 * diffuse[0]);
1986                 cg *= (ambient[1] + 0.5 * diffuse[1]);
1987                 cb *= (ambient[2] + 0.5 * diffuse[2]);
1988         }
1989         if (fogenabled)
1990         {
1991                 fog = VERTEXFOGTABLE(VectorDistance(org, r_vieworigin));
1992                 ifog = 1 - fog;
1993                 cr = cr * ifog;
1994                 cg = cg * ifog;
1995                 cb = cb * ifog;
1996                 if (blendmode == PBLEND_ALPHA)
1997                 {
1998                         cr += fogcolor[0] * fog;
1999                         cg += fogcolor[1] * fog;
2000                         cb += fogcolor[2] * fog;
2001                 }
2002         }
2003
2004         R_Mesh_Matrix(&identitymatrix);
2005
2006         memset(&m, 0, sizeof(m));
2007         m.tex[0] = R_GetTexture(tex->texture);
2008         m.pointer_texcoord[0] = particle_texcoord2f;
2009         m.pointer_vertex = particle_vertex3f;
2010         R_Mesh_State(&m);
2011
2012         GL_Color(cr, cg, cb, ca);
2013
2014         if (blendmode == PBLEND_ALPHA)
2015                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2016         else if (blendmode == PBLEND_ADD)
2017                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2018         else //if (blendmode == PBLEND_MOD)
2019                 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2020         GL_DepthMask(false);
2021         GL_DepthTest(true);
2022         size = p->size * cl_particles_size.value;
2023         if (p->type->orientation == PARTICLE_BILLBOARD || p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2024         {
2025                 if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2026                 {
2027                         // double-sided
2028                         if (DotProduct(p->vel, r_vieworigin) > DotProduct(p->vel, org))
2029                         {
2030                                 VectorNegate(p->vel, v);
2031                                 VectorVectors(v, right, up);
2032                         }
2033                         else
2034                                 VectorVectors(p->vel, right, up);
2035                         VectorScale(right, size, right);
2036                         VectorScale(up, size, up);
2037                 }
2038                 else
2039                 {
2040                         VectorScale(r_viewleft, -size, right);
2041                         VectorScale(r_viewup, size, up);
2042                 }
2043                 particle_vertex3f[ 0] = org[0] - right[0] - up[0];
2044                 particle_vertex3f[ 1] = org[1] - right[1] - up[1];
2045                 particle_vertex3f[ 2] = org[2] - right[2] - up[2];
2046                 particle_vertex3f[ 3] = org[0] - right[0] + up[0];
2047                 particle_vertex3f[ 4] = org[1] - right[1] + up[1];
2048                 particle_vertex3f[ 5] = org[2] - right[2] + up[2];
2049                 particle_vertex3f[ 6] = org[0] + right[0] + up[0];
2050                 particle_vertex3f[ 7] = org[1] + right[1] + up[1];
2051                 particle_vertex3f[ 8] = org[2] + right[2] + up[2];
2052                 particle_vertex3f[ 9] = org[0] + right[0] - up[0];
2053                 particle_vertex3f[10] = org[1] + right[1] - up[1];
2054                 particle_vertex3f[11] = org[2] + right[2] - up[2];
2055                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2056                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2057                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2058                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2059         }
2060         else if (p->type->orientation == PARTICLE_SPARK)
2061         {
2062                 VectorMA(p->org, -0.02, p->vel, v);
2063                 VectorMA(p->org, 0.02, p->vel, up2);
2064                 R_CalcBeam_Vertex3f(particle_vertex3f, v, up2, size);
2065                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2066                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2067                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2068                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2069         }
2070         else if (p->type->orientation == PARTICLE_BEAM)
2071         {
2072                 R_CalcBeam_Vertex3f(particle_vertex3f, p->org, p->vel, size);
2073                 VectorSubtract(p->vel, p->org, up);
2074                 VectorNormalize(up);
2075                 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2076                 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2077                 particle_texcoord2f[0] = 1;particle_texcoord2f[1] = v[0];
2078                 particle_texcoord2f[2] = 0;particle_texcoord2f[3] = v[0];
2079                 particle_texcoord2f[4] = 0;particle_texcoord2f[5] = v[1];
2080                 particle_texcoord2f[6] = 1;particle_texcoord2f[7] = v[1];
2081         }
2082         else
2083         {
2084                 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2085                 return;
2086         }
2087
2088         R_Mesh_Draw(0, 4, 2, polygonelements);
2089 }
2090
2091 void R_DrawParticles (void)
2092 {
2093         int i;
2094         float minparticledist;
2095         particle_t *p;
2096
2097         // LordHavoc: early out conditions
2098         if ((!cl.num_particles) || (!r_drawparticles.integer))
2099                 return;
2100
2101         minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
2102
2103         // LordHavoc: only render if not too close
2104         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2105         {
2106                 if (p->type)
2107                 {
2108                         renderstats.particles++;
2109                         if (DotProduct(p->org, r_viewforward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2110                         {
2111                                 if (p->type == particletype + pt_decal)
2112                                         R_DrawParticle_TransparentCallback(0, i, 0);
2113                                 else
2114                                         R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2115                         }
2116                 }
2117         }
2118 }
2119