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