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