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