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