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