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