]> icculus.org git repositories - divverent/darkplaces.git/blob - cl_particles.c
added keyup function for menu qc at Black's request
[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                 qboolean underwater;
1018                 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1019                 VectorLerp(originmins, 0.5, originmaxs, center);
1020                 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1021                 supercontents = CL_PointSuperContents(center);
1022                 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1023                 VectorSubtract(originmaxs, originmins, traildir);
1024                 VectorNormalize(traildir);
1025                 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1026                 {
1027                         if (info->effectnameindex == effectnameindex)
1028                         {
1029                                 found = true;
1030                                 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1031                                         continue;
1032                                 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1033                                         continue;
1034
1035                                 // spawn a dlight if requested
1036                                 if (info->lightradiusstart > 0)
1037                                 {
1038                                         matrix4x4_t tempmatrix;
1039                                         if (info->trailspacing > 0)
1040                                                 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1041                                         else
1042                                                 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1043                                         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);
1044                                 }
1045
1046                                 // spawn particles
1047                                 tex = info->tex[0];
1048                                 if (info->tex[1] > info->tex[0])
1049                                 {
1050                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1051                                         tex = min(tex, info->tex[1] - 1);
1052                                 }
1053                                 if (info->particletype == pt_decal)
1054                                         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]);
1055                                 else if (info->particletype == pt_beam)
1056                                         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);
1057                                 else
1058                                 {
1059                                         if (!cl_particles.integer)
1060                                                 continue;
1061                                         switch (info->particletype)
1062                                         {
1063                                         case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1064                                         case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1065                                         case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1066                                         case pt_blood: if (!cl_particles_blood.integer) continue;break;
1067                                         default: break;
1068                                         }
1069                                         info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1070                                         VectorCopy(originmins, trailpos);
1071                                         for (;info->particleaccumulator > 0;info->particleaccumulator--)
1072                                         {
1073                                                 if (info->tex[1] > info->tex[0])
1074                                                 {
1075                                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1076                                                         tex = min(tex, info->tex[1] - 1);
1077                                                 }
1078                                                 if (info->trailspacing <= 0)
1079                                                 {
1080                                                         trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1081                                                         trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1082                                                         trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1083                                                 }
1084                                                 VectorRandom(rvec);
1085                                                 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);
1086                                                 if (info->trailspacing > 0)
1087                                                         VectorMA(trailpos, info->trailspacing, traildir, trailpos);
1088                                         }
1089                                 }
1090                         }
1091                 }
1092         }
1093         if (!found)
1094                 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor);
1095 }
1096
1097 /*
1098 ===============
1099 CL_EntityParticles
1100 ===============
1101 */
1102 void CL_EntityParticles (const entity_t *ent)
1103 {
1104         int i;
1105         float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1106         static vec3_t avelocities[NUMVERTEXNORMALS];
1107         if (!cl_particles.integer) return;
1108
1109         Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1110
1111         if (!avelocities[0][0])
1112                 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1113                         avelocities[0][i] = lhrandom(0, 2.55);
1114
1115         for (i = 0;i < NUMVERTEXNORMALS;i++)
1116         {
1117                 yaw = cl.time * avelocities[i][0];
1118                 pitch = cl.time * avelocities[i][1];
1119                 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1120                 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1121                 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1122                 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);
1123         }
1124 }
1125
1126
1127 void CL_ReadPointFile_f (void)
1128 {
1129         vec3_t org, leakorg;
1130         int r, c, s;
1131         char *pointfile = NULL, *pointfilepos, *t, tchar;
1132         char name[MAX_OSPATH];
1133
1134         if (!cl.worldmodel)
1135                 return;
1136
1137         FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1138         strlcat (name, ".pts", sizeof (name));
1139         pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1140         if (!pointfile)
1141         {
1142                 Con_Printf("Could not open %s\n", name);
1143                 return;
1144         }
1145
1146         Con_Printf("Reading %s...\n", name);
1147         VectorClear(leakorg);
1148         c = 0;
1149         s = 0;
1150         pointfilepos = pointfile;
1151         while (*pointfilepos)
1152         {
1153                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1154                         pointfilepos++;
1155                 if (!*pointfilepos)
1156                         break;
1157                 t = pointfilepos;
1158                 while (*t && *t != '\n' && *t != '\r')
1159                         t++;
1160                 tchar = *t;
1161                 *t = 0;
1162                 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1163                 *t = tchar;
1164                 pointfilepos = t;
1165                 if (r != 3)
1166                         break;
1167                 if (c == 0)
1168                         VectorCopy(org, leakorg);
1169                 c++;
1170
1171                 if (cl.num_particles < cl.max_particles - 3)
1172                 {
1173                         s++;
1174                         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);
1175                 }
1176         }
1177         Mem_Free(pointfile);
1178         VectorCopy(leakorg, org);
1179         Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1180
1181         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);
1182         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);
1183         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);
1184 }
1185
1186 /*
1187 ===============
1188 CL_ParseParticleEffect
1189
1190 Parse an effect out of the server message
1191 ===============
1192 */
1193 void CL_ParseParticleEffect (void)
1194 {
1195         vec3_t org, dir;
1196         int i, count, msgcount, color;
1197
1198         MSG_ReadVector(org, cls.protocol);
1199         for (i=0 ; i<3 ; i++)
1200                 dir[i] = MSG_ReadChar ();
1201         msgcount = MSG_ReadByte ();
1202         color = MSG_ReadByte ();
1203
1204         if (msgcount == 255)
1205                 count = 1024;
1206         else
1207                 count = msgcount;
1208
1209         CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1210 }
1211
1212 /*
1213 ===============
1214 CL_ParticleExplosion
1215
1216 ===============
1217 */
1218 void CL_ParticleExplosion (const vec3_t org)
1219 {
1220         int i;
1221         trace_t trace;
1222         //vec3_t v;
1223         //vec3_t v2;
1224         if (cl_stainmaps.integer)
1225                 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1226         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1227
1228         if (cl_particles_quake.integer)
1229         {
1230                 for (i = 0;i < 1024;i++)
1231                 {
1232                         int r, color;
1233                         r = rand()&3;
1234                         if (i & 1)
1235                         {
1236                                 color = particlepalette[ramp1[r]];
1237                                 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);
1238                         }
1239                         else
1240                         {
1241                                 color = particlepalette[ramp2[r]];
1242                                 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);
1243                         }
1244                 }
1245         }
1246         else
1247         {
1248                 i = CL_PointSuperContents(org);
1249                 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1250                 {
1251                         if (cl_particles.integer && cl_particles_bubbles.integer)
1252                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1253                                         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);
1254                 }
1255                 else
1256                 {
1257                         // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1258                         // smoke puff
1259                         if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1260                         {
1261                                 for (i = 0;i < 32;i++)
1262                                 {
1263                                         int k;
1264                                         vec3_t v, v2;
1265                                         for (k = 0;k < 16;k++)
1266                                         {
1267                                                 v[0] = org[0] + lhrandom(-48, 48);
1268                                                 v[1] = org[1] + lhrandom(-48, 48);
1269                                                 v[2] = org[2] + lhrandom(-48, 48);
1270                                                 trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
1271                                                 if (trace.fraction >= 0.1)
1272                                                         break;
1273                                         }
1274                                         VectorSubtract(trace.endpos, org, v2);
1275                                         VectorScale(v2, 2.0f, v2);
1276                                         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);
1277                                 }
1278                         }
1279
1280                         if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1281                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1282                                         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);
1283                 }
1284         }
1285
1286         if (cl_particles_explosions_shell.integer)
1287                 R_NewExplosion(org);
1288 }
1289
1290 /*
1291 ===============
1292 CL_ParticleExplosion2
1293
1294 ===============
1295 */
1296 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1297 {
1298         int i, k;
1299         if (!cl_particles.integer) return;
1300
1301         for (i = 0;i < 512 * cl_particles_quality.value;i++)
1302         {
1303                 k = particlepalette[colorStart + (i % colorLength)];
1304                 if (cl_particles_quake.integer)
1305                         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);
1306                 else
1307                         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);
1308         }
1309 }
1310
1311 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1312 {
1313         if (cl_particles_sparks.integer)
1314         {
1315                 sparkcount *= cl_particles_quality.value;
1316                 while(sparkcount-- > 0)
1317                         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);
1318         }
1319         if (cl_particles_smoke.integer)
1320         {
1321                 smokecount *= cl_particles_quality.value;
1322                 while(smokecount-- > 0)
1323                         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);
1324         }
1325 }
1326
1327 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)
1328 {
1329         int k;
1330         if (!cl_particles.integer) return;
1331
1332         count = (int)(count * cl_particles_quality.value);
1333         while (count--)
1334         {
1335                 k = particlepalette[colorbase + (rand()&3)];
1336                 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);
1337         }
1338 }
1339
1340 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1341 {
1342         int k;
1343         float z, minz, maxz;
1344         particle_t *p;
1345         if (!cl_particles.integer) return;
1346         if (dir[2] < 0) // falling
1347                 z = maxs[2];
1348         else // rising??
1349                 z = mins[2];
1350
1351         minz = z - fabs(dir[2]) * 0.1;
1352         maxz = z + fabs(dir[2]) * 0.1;
1353         minz = bound(mins[2], minz, maxs[2]);
1354         maxz = bound(mins[2], maxz, maxs[2]);
1355
1356         count = (int)(count * cl_particles_quality.value);
1357
1358         switch(type)
1359         {
1360         case 0:
1361                 count *= 4; // ick, this should be in the mod or maps?
1362
1363                 while(count--)
1364                 {
1365                         k = particlepalette[colorbase + (rand()&3)];
1366                         if (gamemode == GAME_GOODVSBAD2)
1367                                 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);
1368                         else
1369                                 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);
1370                 }
1371                 break;
1372         case 1:
1373                 while(count--)
1374                 {
1375                         k = particlepalette[colorbase + (rand()&3)];
1376                         if (gamemode == GAME_GOODVSBAD2)
1377                                 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);
1378                         else
1379                                 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);
1380                         if (p)
1381                                 VectorCopy(p->vel, p->relativedirection);
1382                 }
1383                 break;
1384         default:
1385                 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1386         }
1387 }
1388
1389 /*
1390 ===============
1391 CL_MoveParticles
1392 ===============
1393 */
1394 void CL_MoveParticles (void)
1395 {
1396         particle_t *p;
1397         int i, maxparticle, j, a, content;
1398         float gravity, dvel, bloodwaterfade, frametime, f, dist, org[3], oldorg[3];
1399         int hitent;
1400         trace_t trace;
1401
1402         // LordHavoc: early out condition
1403         if (!cl.num_particles)
1404         {
1405                 cl.free_particle = 0;
1406                 return;
1407         }
1408
1409         frametime = cl.time - cl.oldtime;
1410         gravity = frametime * sv_gravity.value;
1411         dvel = 1+4*frametime;
1412         bloodwaterfade = max(cl_particles_blood_alpha.value, 0.01f) * frametime * 128.0f;
1413
1414         maxparticle = -1;
1415         j = 0;
1416         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1417         {
1418                 if (!p->type)
1419                         continue;
1420                 maxparticle = i;
1421                 content = 0;
1422
1423                 p->alpha -= p->alphafade * frametime;
1424
1425                 if (p->alpha <= 0)
1426                 {
1427                         p->type = NULL;
1428                         continue;
1429                 }
1430
1431                 if (p->type->orientation != PARTICLE_BEAM)
1432                 {
1433                         VectorCopy(p->org, oldorg);
1434                         VectorMA(p->org, frametime, p->vel, p->org);
1435                         VectorCopy(p->org, org);
1436                         if (p->bounce)
1437                         {
1438                                 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);
1439                                 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1440                                 // or if the trace hit something flagged as NOIMPACT
1441                                 // then remove the particle
1442                                 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1443                                 {
1444                                         p->type = NULL;
1445                                         continue;
1446                                 }
1447                                 // react if the particle hit something
1448                                 if (trace.fraction < 1)
1449                                 {
1450                                         VectorCopy(trace.endpos, p->org);
1451                                         if (p->type == particletype + pt_rain)
1452                                         {
1453                                                 // raindrop - splash on solid/water/slime/lava
1454                                                 int count;
1455                                                 // convert from a raindrop particle to a rainsplash decal
1456                                                 VectorCopy(trace.plane.normal, p->vel);
1457                                                 VectorAdd(p->org, p->vel, p->org);
1458                                                 p->type = particletype + pt_raindecal;
1459                                                 p->texnum = tex_rainsplash;
1460                                                 p->time2 = cl.time;
1461                                                 p->alphafade = p->alpha / 0.4;
1462                                                 p->bounce = 0;
1463                                                 p->friction = 0;
1464                                                 p->gravity = 0;
1465                                                 p->size *= 1.0f;
1466                                                 p->sizeincrease = p->size * 16;
1467                                                 count = rand() & 3;
1468                                                 while(count--)
1469                                                         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);
1470                                         }
1471                                         else if (p->type == particletype + pt_blood)
1472                                         {
1473                                                 // blood - splash on solid
1474                                                 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1475                                                 {
1476                                                         p->type = NULL;
1477                                                         continue;
1478                                                 }
1479                                                 if (!cl_decals.integer)
1480                                                 {
1481                                                         p->type = NULL;
1482                                                         continue;
1483                                                 }
1484                                                 // convert from a blood particle to a blood decal
1485                                                 VectorCopy(trace.plane.normal, p->vel);
1486                                                 VectorAdd(p->org, p->vel, p->org);
1487                                                 if (cl_stainmaps.integer)
1488                                                         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)));
1489
1490                                                 p->type = particletype + pt_decal;
1491                                                 p->texnum = tex_blooddecal[rand()&7];
1492                                                 p->owner = hitent;
1493                                                 p->ownermodel = cl.entities[hitent].render.model;
1494                                                 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1495                                                 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1496                                                 p->time2 = cl.time;
1497                                                 p->alphafade = 0;
1498                                                 p->bounce = 0;
1499                                                 p->friction = 0;
1500                                                 p->gravity = 0;
1501                                                 p->size *= 2.0f;
1502                                         }
1503                                         else if (p->bounce < 0)
1504                                         {
1505                                                 // bounce -1 means remove on impact
1506                                                 p->type = NULL;
1507                                                 continue;
1508                                         }
1509                                         else
1510                                         {
1511                                                 // anything else - bounce off solid
1512                                                 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1513                                                 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1514                                                 if (DotProduct(p->vel, p->vel) < 0.03)
1515                                                         VectorClear(p->vel);
1516                                         }
1517                                 }
1518                         }
1519                         p->vel[2] -= p->gravity * gravity;
1520
1521                         if (p->friction)
1522                         {
1523                                 f = p->friction * frametime;
1524                                 if (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1525                                         f *= 4;
1526                                 f = 1.0f - f;
1527                                 VectorScale(p->vel, f, p->vel);
1528                         }
1529                 }
1530
1531                 if (p->type != particletype + pt_static)
1532                 {
1533                         switch (p->type - particletype)
1534                         {
1535                         case pt_entityparticle:
1536                                 // particle that removes itself after one rendered frame
1537                                 if (p->time2)
1538                                         p->type = NULL;
1539                                 else
1540                                         p->time2 = 1;
1541                                 break;
1542                         case pt_blood:
1543                                 a = CL_PointSuperContents(p->org);
1544                                 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1545                                 {
1546                                         p->size += frametime * 8;
1547                                         //p->alpha -= bloodwaterfade;
1548                                 }
1549                                 else
1550                                         p->vel[2] -= gravity;
1551                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1552                                         p->type = NULL;
1553                                 break;
1554                         case pt_bubble:
1555                                 a = CL_PointSuperContents(p->org);
1556                                 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1557                                 {
1558                                         p->type = NULL;
1559                                         break;
1560                                 }
1561                                 break;
1562                         case pt_rain:
1563                                 a = CL_PointSuperContents(p->org);
1564                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1565                                         p->type = NULL;
1566                                 break;
1567                         case pt_snow:
1568                                 if (cl.time > p->time2)
1569                                 {
1570                                         // snow flutter
1571                                         p->time2 = cl.time + (rand() & 3) * 0.1;
1572                                         p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1573                                         p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1574                                         //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1575                                 }
1576                                 a = CL_PointSuperContents(p->org);
1577                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1578                                         p->type = NULL;
1579                                 break;
1580                         case pt_smoke:
1581                                 //p->size += frametime * 15;
1582                                 break;
1583                         case pt_decal:
1584                                 // FIXME: this has fairly wacky handling of alpha
1585                                 p->alphafade = cl.time > (p->time2 + cl_decals_time.value) ? (255 / cl_decals_fadetime.value) : 0;
1586                                 if (cl.entities[p->owner].render.model == p->ownermodel)
1587                                 {
1588                                         Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1589                                         Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1590                                 }
1591                                 else
1592                                         p->type = NULL;
1593                                 break;
1594                         default:
1595                                 break;
1596                         }
1597                 }
1598         }
1599         cl.num_particles = maxparticle + 1;
1600         cl.free_particle = 0;
1601 }
1602
1603 #define MAX_PARTICLETEXTURES 64
1604 // particletexture_t is a rectangle in the particlefonttexture
1605 typedef struct particletexture_s
1606 {
1607         rtexture_t *texture;
1608         float s1, t1, s2, t2;
1609 }
1610 particletexture_t;
1611
1612 static rtexturepool_t *particletexturepool;
1613 static rtexture_t *particlefonttexture;
1614 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1615
1616 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1617
1618 #define PARTICLETEXTURESIZE 64
1619 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1620
1621 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1622 {
1623         float dz, f, dot;
1624         vec3_t normal;
1625         dz = 1 - (dx*dx+dy*dy);
1626         if (dz > 0) // it does hit the sphere
1627         {
1628                 f = 0;
1629                 // back side
1630                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1631                 VectorNormalize(normal);
1632                 dot = DotProduct(normal, light);
1633                 if (dot > 0.5) // interior reflection
1634                         f += ((dot *  2) - 1);
1635                 else if (dot < -0.5) // exterior reflection
1636                         f += ((dot * -2) - 1);
1637                 // front side
1638                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1639                 VectorNormalize(normal);
1640                 dot = DotProduct(normal, light);
1641                 if (dot > 0.5) // interior reflection
1642                         f += ((dot *  2) - 1);
1643                 else if (dot < -0.5) // exterior reflection
1644                         f += ((dot * -2) - 1);
1645                 f *= 128;
1646                 f += 16; // just to give it a haze so you can see the outline
1647                 f = bound(0, f, 255);
1648                 return (unsigned char) f;
1649         }
1650         else
1651                 return 0;
1652 }
1653
1654 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1655 {
1656         int basex, basey, y;
1657         basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1658         basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1659         particletexture[texnum].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1660         particletexture[texnum].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1661         particletexture[texnum].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1662         particletexture[texnum].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1663         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1664                 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1665 }
1666
1667 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1668 {
1669         int x, y;
1670         float cx, cy, dx, dy, f, iradius;
1671         unsigned char *d;
1672         cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1673         cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1674         iradius = 1.0f / radius;
1675         alpha *= (1.0f / 255.0f);
1676         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1677         {
1678                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1679                 {
1680                         dx = (x - cx);
1681                         dy = (y - cy);
1682                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1683                         if (f > 0)
1684                         {
1685                                 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1686                                 d[0] += (int)(f * (red   - d[0]));
1687                                 d[1] += (int)(f * (green - d[1]));
1688                                 d[2] += (int)(f * (blue  - d[2]));
1689                         }
1690                 }
1691         }
1692 }
1693
1694 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1695 {
1696         int i;
1697         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1698         {
1699                 data[0] = bound(minr, data[0], maxr);
1700                 data[1] = bound(ming, data[1], maxg);
1701                 data[2] = bound(minb, data[2], maxb);
1702         }
1703 }
1704
1705 void particletextureinvert(unsigned char *data)
1706 {
1707         int i;
1708         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1709         {
1710                 data[0] = 255 - data[0];
1711                 data[1] = 255 - data[1];
1712                 data[2] = 255 - data[2];
1713         }
1714 }
1715
1716 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1717 static void R_InitBloodTextures (unsigned char *particletexturedata)
1718 {
1719         int i, j, k, m;
1720         unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1721
1722         // blood particles
1723         for (i = 0;i < 8;i++)
1724         {
1725                 memset(&data[0][0][0], 255, sizeof(data));
1726                 for (k = 0;k < 24;k++)
1727                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1728                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1729                 particletextureinvert(&data[0][0][0]);
1730                 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1731         }
1732
1733         // blood decals
1734         for (i = 0;i < 8;i++)
1735         {
1736                 memset(&data[0][0][0], 255, sizeof(data));
1737                 m = 8;
1738                 for (j = 1;j < 10;j++)
1739                         for (k = min(j, m - 1);k < m;k++)
1740                                 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1741                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1742                 particletextureinvert(&data[0][0][0]);
1743                 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1744         }
1745
1746 }
1747
1748 static void R_InitParticleTexture (void)
1749 {
1750         int x, y, d, i, k, m;
1751         float dx, dy, f;
1752         unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4], noise3[64][64], data2[64][16][4];
1753         vec3_t light;
1754         unsigned char *particletexturedata;
1755
1756         // a note: decals need to modulate (multiply) the background color to
1757         // properly darken it (stain), and they need to be able to alpha fade,
1758         // this is a very difficult challenge because it means fading to white
1759         // (no change to background) rather than black (darkening everything
1760         // behind the whole decal polygon), and to accomplish this the texture is
1761         // inverted (dark red blood on white background becomes brilliant cyan
1762         // and white on black background) so we can alpha fade it to black, then
1763         // we invert it again during the blendfunc to make it work...
1764
1765         particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1766         memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1767
1768         // smoke
1769         for (i = 0;i < 8;i++)
1770         {
1771                 memset(&data[0][0][0], 255, sizeof(data));
1772                 do
1773                 {
1774                         unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1775
1776                         fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1777                         fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1778                         m = 0;
1779                         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1780                         {
1781                                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1782                                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1783                                 {
1784                                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1785                                         d = (noise2[y][x] - 128) * 3 + 192;
1786                                         if (d > 0)
1787                                                 d = (int)(d * (1-(dx*dx+dy*dy)));
1788                                         d = (d * noise1[y][x]) >> 7;
1789                                         d = bound(0, d, 255);
1790                                         data[y][x][3] = (unsigned char) d;
1791                                         if (m < d)
1792                                                 m = d;
1793                                 }
1794                         }
1795                 }
1796                 while (m < 224);
1797                 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1798         }
1799
1800         // rain splash
1801         memset(&data[0][0][0], 255, sizeof(data));
1802         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1803         {
1804                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1805                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1806                 {
1807                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1808                         f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1809                         data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1810                 }
1811         }
1812         setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1813
1814         // normal particle
1815         memset(&data[0][0][0], 255, sizeof(data));
1816         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1817         {
1818                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1819                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1820                 {
1821                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1822                         d = (int)(256 * (1 - (dx*dx+dy*dy)));
1823                         d = bound(0, d, 255);
1824                         data[y][x][3] = (unsigned char) d;
1825                 }
1826         }
1827         setuptex(tex_particle, &data[0][0][0], particletexturedata);
1828
1829         // rain
1830         memset(&data[0][0][0], 255, sizeof(data));
1831         light[0] = 1;light[1] = 1;light[2] = 1;
1832         VectorNormalize(light);
1833         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1834         {
1835                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1836                 // stretch upper half of bubble by +50% and shrink lower half by -50%
1837                 // (this gives an elongated teardrop shape)
1838                 if (dy > 0.5f)
1839                         dy = (dy - 0.5f) * 2.0f;
1840                 else
1841                         dy = (dy - 0.5f) / 1.5f;
1842                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1843                 {
1844                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1845                         // shrink bubble width to half
1846                         dx *= 2.0f;
1847                         data[y][x][3] = shadebubble(dx, dy, light);
1848                 }
1849         }
1850         setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1851
1852         // bubble
1853         memset(&data[0][0][0], 255, sizeof(data));
1854         light[0] = 1;light[1] = 1;light[2] = 1;
1855         VectorNormalize(light);
1856         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1857         {
1858                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1859                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1860                 {
1861                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1862                         data[y][x][3] = shadebubble(dx, dy, light);
1863                 }
1864         }
1865         setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1866
1867         // Blood particles and blood decals
1868         R_InitBloodTextures (particletexturedata);
1869
1870         // bullet decals
1871         for (i = 0;i < 8;i++)
1872         {
1873                 memset(&data[0][0][0], 255, sizeof(data));
1874                 for (k = 0;k < 12;k++)
1875                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1876                 for (k = 0;k < 3;k++)
1877                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1878                 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1879                 particletextureinvert(&data[0][0][0]);
1880                 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1881         }
1882
1883 #if 0
1884         Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1885 #endif
1886
1887         particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1888         if (!particlefonttexture)
1889                 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1890         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1891                 particletexture[i].texture = particlefonttexture;
1892
1893         // nexbeam
1894         fractalnoise(&noise3[0][0], 64, 4);
1895         m = 0;
1896         for (y = 0;y < 64;y++)
1897         {
1898                 dy = (y - 0.5f*64) / (64*0.5f-1);
1899                 for (x = 0;x < 16;x++)
1900                 {
1901                         dx = (x - 0.5f*16) / (16*0.5f-2);
1902                         d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1903                         data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1904                         data2[y][x][3] = 255;
1905                 }
1906         }
1907
1908 #if 0
1909         Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1910 #endif
1911
1912         particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1913         if (!particletexture[tex_beam].texture)
1914                 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1915         particletexture[tex_beam].s1 = 0;
1916         particletexture[tex_beam].t1 = 0;
1917         particletexture[tex_beam].s2 = 1;
1918         particletexture[tex_beam].t2 = 1;
1919         Mem_Free(particletexturedata);
1920 }
1921
1922 static void r_part_start(void)
1923 {
1924         particletexturepool = R_AllocTexturePool();
1925         R_InitParticleTexture ();
1926         CL_Particles_LoadEffectInfo();
1927 }
1928
1929 static void r_part_shutdown(void)
1930 {
1931         R_FreeTexturePool(&particletexturepool);
1932 }
1933
1934 static void r_part_newmap(void)
1935 {
1936 }
1937
1938 void R_Particles_Init (void)
1939 {
1940         Cvar_RegisterVariable(&r_drawparticles);
1941         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1942 }
1943
1944 float particle_vertex3f[12], particle_texcoord2f[8];
1945
1946 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight)
1947 {
1948         const particle_t *p = cl.particles + surfacenumber;
1949         rmeshstate_t m;
1950         pblend_t blendmode;
1951         float org[3], up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
1952         particletexture_t *tex;
1953
1954         VectorCopy(p->org, org);
1955
1956         blendmode = p->type->blendmode;
1957         tex = &particletexture[p->texnum];
1958         cr = p->color[0] * (1.0f / 255.0f);
1959         cg = p->color[1] * (1.0f / 255.0f);
1960         cb = p->color[2] * (1.0f / 255.0f);
1961         ca = p->alpha * (1.0f / 255.0f);
1962         if (blendmode == PBLEND_MOD)
1963         {
1964                 cr *= ca;
1965                 cg *= ca;
1966                 cb *= ca;
1967                 cr = min(cr, 1);
1968                 cg = min(cg, 1);
1969                 cb = min(cb, 1);
1970                 ca = 1;
1971         }
1972         ca /= cl_particles_quality.value;
1973         if (p->type->lighting)
1974         {
1975                 float ambient[3], diffuse[3], diffusenormal[3];
1976                 R_CompleteLightPoint(ambient, diffuse, diffusenormal, org, true);
1977                 cr *= (ambient[0] + 0.5 * diffuse[0]);
1978                 cg *= (ambient[1] + 0.5 * diffuse[1]);
1979                 cb *= (ambient[2] + 0.5 * diffuse[2]);
1980         }
1981         if (fogenabled)
1982         {
1983                 fog = VERTEXFOGTABLE(VectorDistance(org, r_vieworigin));
1984                 ifog = 1 - fog;
1985                 cr = cr * ifog;
1986                 cg = cg * ifog;
1987                 cb = cb * ifog;
1988                 if (blendmode == PBLEND_ALPHA)
1989                 {
1990                         cr += fogcolor[0] * fog;
1991                         cg += fogcolor[1] * fog;
1992                         cb += fogcolor[2] * fog;
1993                 }
1994         }
1995
1996         R_Mesh_Matrix(&identitymatrix);
1997
1998         R_Mesh_VertexPointer(particle_vertex3f);
1999         R_Mesh_ColorPointer(NULL);
2000         memset(&m, 0, sizeof(m));
2001         m.tex[0] = R_GetTexture(tex->texture);
2002         m.pointer_texcoord[0] = particle_texcoord2f;
2003         R_Mesh_TextureState(&m);
2004
2005         GL_Color(cr, cg, cb, ca);
2006
2007         if (blendmode == PBLEND_ALPHA)
2008                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2009         else if (blendmode == PBLEND_ADD)
2010                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2011         else //if (blendmode == PBLEND_MOD)
2012                 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2013         GL_DepthMask(false);
2014         GL_DepthTest(true);
2015         size = p->size * cl_particles_size.value;
2016         if (p->type->orientation == PARTICLE_BILLBOARD || p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2017         {
2018                 if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2019                 {
2020                         // double-sided
2021                         if (DotProduct(p->vel, r_vieworigin) > DotProduct(p->vel, org))
2022                         {
2023                                 VectorNegate(p->vel, v);
2024                                 VectorVectors(v, right, up);
2025                         }
2026                         else
2027                                 VectorVectors(p->vel, right, up);
2028                         VectorScale(right, size, right);
2029                         VectorScale(up, size, up);
2030                 }
2031                 else
2032                 {
2033                         VectorScale(r_viewleft, -size, right);
2034                         VectorScale(r_viewup, size, up);
2035                 }
2036                 particle_vertex3f[ 0] = org[0] - right[0] - up[0];
2037                 particle_vertex3f[ 1] = org[1] - right[1] - up[1];
2038                 particle_vertex3f[ 2] = org[2] - right[2] - up[2];
2039                 particle_vertex3f[ 3] = org[0] - right[0] + up[0];
2040                 particle_vertex3f[ 4] = org[1] - right[1] + up[1];
2041                 particle_vertex3f[ 5] = org[2] - right[2] + up[2];
2042                 particle_vertex3f[ 6] = org[0] + right[0] + up[0];
2043                 particle_vertex3f[ 7] = org[1] + right[1] + up[1];
2044                 particle_vertex3f[ 8] = org[2] + right[2] + up[2];
2045                 particle_vertex3f[ 9] = org[0] + right[0] - up[0];
2046                 particle_vertex3f[10] = org[1] + right[1] - up[1];
2047                 particle_vertex3f[11] = org[2] + right[2] - up[2];
2048                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2049                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2050                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2051                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2052         }
2053         else if (p->type->orientation == PARTICLE_SPARK)
2054         {
2055                 VectorMA(p->org, -0.02, p->vel, v);
2056                 VectorMA(p->org, 0.02, p->vel, up2);
2057                 R_CalcBeam_Vertex3f(particle_vertex3f, v, up2, size);
2058                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2059                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2060                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2061                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2062         }
2063         else if (p->type->orientation == PARTICLE_BEAM)
2064         {
2065                 R_CalcBeam_Vertex3f(particle_vertex3f, p->org, p->vel, size);
2066                 VectorSubtract(p->vel, p->org, up);
2067                 VectorNormalize(up);
2068                 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2069                 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2070                 particle_texcoord2f[0] = 1;particle_texcoord2f[1] = v[0];
2071                 particle_texcoord2f[2] = 0;particle_texcoord2f[3] = v[0];
2072                 particle_texcoord2f[4] = 0;particle_texcoord2f[5] = v[1];
2073                 particle_texcoord2f[6] = 1;particle_texcoord2f[7] = v[1];
2074         }
2075         else
2076         {
2077                 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2078                 return;
2079         }
2080
2081         R_Mesh_Draw(0, 4, 2, polygonelements);
2082 }
2083
2084 void R_DrawParticles (void)
2085 {
2086         int i;
2087         float minparticledist;
2088         particle_t *p;
2089
2090         // LordHavoc: early out conditions
2091         if ((!cl.num_particles) || (!r_drawparticles.integer))
2092                 return;
2093
2094         minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
2095
2096         // LordHavoc: only render if not too close
2097         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2098         {
2099                 if (p->type)
2100                 {
2101                         renderstats.particles++;
2102                         if (DotProduct(p->org, r_viewforward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2103                         {
2104                                 if (p->type == particletype + pt_decal)
2105                                         R_DrawParticle_TransparentCallback(0, i, 0);
2106                                 else
2107                                         R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2108                         }
2109                 }
2110         }
2111 }
2112