]> icculus.org git repositories - divverent/darkplaces.git/blob - cl_particles.c
split RSurf_DrawBatch_Lightmap function into 4 main functions (previously there were...
[divverent/darkplaces.git] / cl_particles.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "quakedef.h"
22
23 #include "cl_collision.h"
24 #include "image.h"
25 #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                 if (ent)
853                 {
854                         dec = -ent->persistent.trail_time;
855                         ent->persistent.trail_time += len;
856                         if (ent->persistent.trail_time < 0.01f)
857                                 return;
858
859                         // if we skip out, leave it reset
860                         ent->persistent.trail_time = 0.0f;
861                 }
862                 else
863                         dec = 0;
864
865                 // advance into this frame to reach the first puff location
866                 VectorMA(originmins, dec, dir, pos);
867                 len -= dec;
868
869                 smoke = cl_particles.integer && cl_particles_smoke.integer;
870                 blood = cl_particles.integer && cl_particles_blood.integer;
871                 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
872                 qd = 1.0f / cl_particles_quality.value;
873
874                 while (len >= 0)
875                 {
876                         dec = 3;
877                         if (blood)
878                         {
879                                 if (effectnameindex == EFFECT_TR_BLOOD)
880                                 {
881                                         if (cl_particles_quake.integer)
882                                         {
883                                                 color = particlepalette[67 + (rand()&3)];
884                                                 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);
885                                         }
886                                         else
887                                         {
888                                                 dec = 16;
889                                                 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);
890                                         }
891                                 }
892                                 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
893                                 {
894                                         if (cl_particles_quake.integer)
895                                         {
896                                                 dec = 6;
897                                                 color = particlepalette[67 + (rand()&3)];
898                                                 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);
899                                         }
900                                         else
901                                         {
902                                                 dec = 32;
903                                                 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);
904                                         }
905                                 }
906                         }
907                         if (smoke)
908                         {
909                                 if (effectnameindex == EFFECT_TR_ROCKET)
910                                 {
911                                         if (cl_particles_quake.integer)
912                                         {
913                                                 r = rand()&3;
914                                                 color = particlepalette[ramp3[r]];
915                                                 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);
916                                         }
917                                         else
918                                         {
919                                                 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);
920                                                 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);
921                                         }
922                                 }
923                                 else if (effectnameindex == EFFECT_TR_GRENADE)
924                                 {
925                                         if (cl_particles_quake.integer)
926                                         {
927                                                 r = 2 + (rand()%5);
928                                                 color = particlepalette[ramp3[r]];
929                                                 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);
930                                         }
931                                         else
932                                         {
933                                                 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);
934                                         }
935                                 }
936                                 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
937                                 {
938                                         if (cl_particles_quake.integer)
939                                         {
940                                                 dec = 6;
941                                                 color = particlepalette[52 + (rand()&7)];
942                                                 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
943                                                 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
944                                         }
945                                         else if (gamemode == GAME_GOODVSBAD2)
946                                         {
947                                                 dec = 6;
948                                                 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);
949                                         }
950                                         else
951                                         {
952                                                 color = particlepalette[20 + (rand()&7)];
953                                                 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);
954                                         }
955                                 }
956                                 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
957                                 {
958                                         if (cl_particles_quake.integer)
959                                         {
960                                                 dec = 6;
961                                                 color = particlepalette[230 + (rand()&7)];
962                                                 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);
963                                                 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);
964                                         }
965                                         else
966                                         {
967                                                 color = particlepalette[226 + (rand()&7)];
968                                                 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);
969                                         }
970                                 }
971                                 else if (effectnameindex == EFFECT_TR_VORESPIKE)
972                                 {
973                                         if (cl_particles_quake.integer)
974                                         {
975                                                 color = particlepalette[152 + (rand()&3)];
976                                                 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);
977                                         }
978                                         else if (gamemode == GAME_GOODVSBAD2)
979                                         {
980                                                 dec = 6;
981                                                 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);
982                                         }
983                                         else if (gamemode == GAME_PRYDON)
984                                         {
985                                                 dec = 6;
986                                                 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);
987                                         }
988                                         else
989                                                 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);
990                                 }
991                                 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
992                                 {
993                                         dec = 7;
994                                         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);
995                                 }
996                                 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
997                                 {
998                                         dec = 4;
999                                         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);
1000                                 }
1001                                 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1002                                         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);
1003                         }
1004                         if (bubbles)
1005                         {
1006                                 if (effectnameindex == EFFECT_TR_ROCKET)
1007                                         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);
1008                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1009                                         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);
1010                         }
1011                         // advance to next time and position
1012                         dec *= qd;
1013                         len -= dec;
1014                         VectorMA (pos, dec, dir, pos);
1015                 }
1016                 if (ent)
1017                         ent->persistent.trail_time = len;
1018         }
1019         else if (developer.integer >= 1)
1020                 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1021 }
1022
1023 // this is also called on point effects with spawndlight = true and
1024 // spawnparticles = true
1025 // it is called CL_ParticleTrail because most code does not want to supply
1026 // these parameters, only trail handling does
1027 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)
1028 {
1029         vec3_t center;
1030         qboolean found = false;
1031         if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1032                 return; // invalid effect index
1033         if (!particleeffectname[effectnameindex][0])
1034                 return; // no such effect
1035         VectorLerp(originmins, 0.5, originmaxs, center);
1036         if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1037         {
1038                 int effectinfoindex;
1039                 int supercontents;
1040                 int tex;
1041                 particleeffectinfo_t *info;
1042                 vec3_t center;
1043                 vec3_t centervelocity;
1044                 vec3_t traildir;
1045                 vec3_t trailpos;
1046                 vec3_t rvec;
1047                 vec_t traillen;
1048                 vec_t trailstep;
1049                 qboolean underwater;
1050                 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1051                 VectorLerp(originmins, 0.5, originmaxs, center);
1052                 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1053                 supercontents = CL_PointSuperContents(center);
1054                 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1055                 VectorSubtract(originmaxs, originmins, traildir);
1056                 traillen = VectorLength(traildir);
1057                 VectorNormalize(traildir);
1058                 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1059                 {
1060                         if (info->effectnameindex == effectnameindex)
1061                         {
1062                                 found = true;
1063                                 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1064                                         continue;
1065                                 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1066                                         continue;
1067
1068                                 // spawn a dlight if requested
1069                                 if (info->lightradiusstart > 0 && spawndlight)
1070                                 {
1071                                         matrix4x4_t tempmatrix;
1072                                         if (info->trailspacing > 0)
1073                                                 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1074                                         else
1075                                                 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1076                                         if (info->lighttime > 0 && info->lightradiusfade > 0)
1077                                         {
1078                                                 // light flash (explosion, etc)
1079                                                 // called when effect starts
1080                                                 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);
1081                                         }
1082                                         else
1083                                         {
1084                                                 // glowing entity
1085                                                 // called by CL_LinkNetworkEntity
1086                                                 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1087                                                 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);
1088                                         }
1089                                 }
1090
1091                                 if (!spawnparticles)
1092                                         continue;
1093
1094                                 // spawn particles
1095                                 tex = info->tex[0];
1096                                 if (info->tex[1] > info->tex[0])
1097                                 {
1098                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1099                                         tex = min(tex, info->tex[1] - 1);
1100                                 }
1101                                 if (info->particletype == pt_decal)
1102                                         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]);
1103                                 else if (info->particletype == pt_beam)
1104                                         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);
1105                                 else
1106                                 {
1107                                         if (!cl_particles.integer)
1108                                                 continue;
1109                                         switch (info->particletype)
1110                                         {
1111                                         case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1112                                         case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1113                                         case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1114                                         case pt_blood: if (!cl_particles_blood.integer) continue;break;
1115                                         default: break;
1116                                         }
1117                                         VectorCopy(originmins, trailpos);
1118                                         if (info->trailspacing > 0)
1119                                         {
1120                                                 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1121                                                 trailstep = info->trailspacing / cl_particles_quality.value;
1122                                         }
1123                                         else
1124                                         {
1125                                                 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1126                                                 trailstep = 0;
1127                                         }
1128                                         info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1129                                         for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1130                                         {
1131                                                 if (info->tex[1] > info->tex[0])
1132                                                 {
1133                                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1134                                                         tex = min(tex, info->tex[1] - 1);
1135                                                 }
1136                                                 if (!trailstep)
1137                                                 {
1138                                                         trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1139                                                         trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1140                                                         trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1141                                                 }
1142                                                 VectorRandom(rvec);
1143                                                 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);
1144                                                 if (trailstep)
1145                                                         VectorMA(trailpos, trailstep, traildir, trailpos);
1146                                         }
1147                                 }
1148                         }
1149                 }
1150         }
1151         if (!found)
1152                 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1153 }
1154
1155 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)
1156 {
1157         CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1158 }
1159
1160 /*
1161 ===============
1162 CL_EntityParticles
1163 ===============
1164 */
1165 void CL_EntityParticles (const entity_t *ent)
1166 {
1167         int i;
1168         float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1169         static vec3_t avelocities[NUMVERTEXNORMALS];
1170         if (!cl_particles.integer) return;
1171
1172         Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1173
1174         if (!avelocities[0][0])
1175                 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1176                         avelocities[0][i] = lhrandom(0, 2.55);
1177
1178         for (i = 0;i < NUMVERTEXNORMALS;i++)
1179         {
1180                 yaw = cl.time * avelocities[i][0];
1181                 pitch = cl.time * avelocities[i][1];
1182                 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1183                 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1184                 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1185                 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);
1186         }
1187 }
1188
1189
1190 void CL_ReadPointFile_f (void)
1191 {
1192         vec3_t org, leakorg;
1193         int r, c, s;
1194         char *pointfile = NULL, *pointfilepos, *t, tchar;
1195         char name[MAX_OSPATH];
1196
1197         if (!cl.worldmodel)
1198                 return;
1199
1200         FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1201         strlcat (name, ".pts", sizeof (name));
1202         pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1203         if (!pointfile)
1204         {
1205                 Con_Printf("Could not open %s\n", name);
1206                 return;
1207         }
1208
1209         Con_Printf("Reading %s...\n", name);
1210         VectorClear(leakorg);
1211         c = 0;
1212         s = 0;
1213         pointfilepos = pointfile;
1214         while (*pointfilepos)
1215         {
1216                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1217                         pointfilepos++;
1218                 if (!*pointfilepos)
1219                         break;
1220                 t = pointfilepos;
1221                 while (*t && *t != '\n' && *t != '\r')
1222                         t++;
1223                 tchar = *t;
1224                 *t = 0;
1225                 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1226                 *t = tchar;
1227                 pointfilepos = t;
1228                 if (r != 3)
1229                         break;
1230                 if (c == 0)
1231                         VectorCopy(org, leakorg);
1232                 c++;
1233
1234                 if (cl.num_particles < cl.max_particles - 3)
1235                 {
1236                         s++;
1237                         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);
1238                 }
1239         }
1240         Mem_Free(pointfile);
1241         VectorCopy(leakorg, org);
1242         Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1243
1244         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);
1245         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);
1246         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);
1247 }
1248
1249 /*
1250 ===============
1251 CL_ParseParticleEffect
1252
1253 Parse an effect out of the server message
1254 ===============
1255 */
1256 void CL_ParseParticleEffect (void)
1257 {
1258         vec3_t org, dir;
1259         int i, count, msgcount, color;
1260
1261         MSG_ReadVector(org, cls.protocol);
1262         for (i=0 ; i<3 ; i++)
1263                 dir[i] = MSG_ReadChar ();
1264         msgcount = MSG_ReadByte ();
1265         color = MSG_ReadByte ();
1266
1267         if (msgcount == 255)
1268                 count = 1024;
1269         else
1270                 count = msgcount;
1271
1272         CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1273 }
1274
1275 /*
1276 ===============
1277 CL_ParticleExplosion
1278
1279 ===============
1280 */
1281 void CL_ParticleExplosion (const vec3_t org)
1282 {
1283         int i;
1284         trace_t trace;
1285         //vec3_t v;
1286         //vec3_t v2;
1287         if (cl_stainmaps.integer)
1288                 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1289         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1290
1291         if (cl_particles_quake.integer)
1292         {
1293                 for (i = 0;i < 1024;i++)
1294                 {
1295                         int r, color;
1296                         r = rand()&3;
1297                         if (i & 1)
1298                         {
1299                                 color = particlepalette[ramp1[r]];
1300                                 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);
1301                         }
1302                         else
1303                         {
1304                                 color = particlepalette[ramp2[r]];
1305                                 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);
1306                         }
1307                 }
1308         }
1309         else
1310         {
1311                 i = CL_PointSuperContents(org);
1312                 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1313                 {
1314                         if (cl_particles.integer && cl_particles_bubbles.integer)
1315                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1316                                         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);
1317                 }
1318                 else
1319                 {
1320                         // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1321                         // smoke puff
1322                         if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1323                         {
1324                                 for (i = 0;i < 32;i++)
1325                                 {
1326                                         int k;
1327                                         vec3_t v, v2;
1328                                         for (k = 0;k < 16;k++)
1329                                         {
1330                                                 v[0] = org[0] + lhrandom(-48, 48);
1331                                                 v[1] = org[1] + lhrandom(-48, 48);
1332                                                 v[2] = org[2] + lhrandom(-48, 48);
1333                                                 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1334                                                 if (trace.fraction >= 0.1)
1335                                                         break;
1336                                         }
1337                                         VectorSubtract(trace.endpos, org, v2);
1338                                         VectorScale(v2, 2.0f, v2);
1339                                         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);
1340                                 }
1341                         }
1342
1343                         if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1344                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1345                                         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);
1346                 }
1347         }
1348
1349         if (cl_particles_explosions_shell.integer)
1350                 R_NewExplosion(org);
1351 }
1352
1353 /*
1354 ===============
1355 CL_ParticleExplosion2
1356
1357 ===============
1358 */
1359 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1360 {
1361         int i, k;
1362         if (!cl_particles.integer) return;
1363
1364         for (i = 0;i < 512 * cl_particles_quality.value;i++)
1365         {
1366                 k = particlepalette[colorStart + (i % colorLength)];
1367                 if (cl_particles_quake.integer)
1368                         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);
1369                 else
1370                         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);
1371         }
1372 }
1373
1374 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1375 {
1376         if (cl_particles_sparks.integer)
1377         {
1378                 sparkcount *= cl_particles_quality.value;
1379                 while(sparkcount-- > 0)
1380                         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);
1381         }
1382         if (cl_particles_smoke.integer)
1383         {
1384                 smokecount *= cl_particles_quality.value;
1385                 while(smokecount-- > 0)
1386                         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);
1387         }
1388 }
1389
1390 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)
1391 {
1392         int k;
1393         if (!cl_particles.integer) return;
1394
1395         count = (int)(count * cl_particles_quality.value);
1396         while (count--)
1397         {
1398                 k = particlepalette[colorbase + (rand()&3)];
1399                 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);
1400         }
1401 }
1402
1403 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1404 {
1405         int k;
1406         float z, minz, maxz;
1407         particle_t *p;
1408         if (!cl_particles.integer) return;
1409         if (dir[2] < 0) // falling
1410                 z = maxs[2];
1411         else // rising??
1412                 z = mins[2];
1413
1414         minz = z - fabs(dir[2]) * 0.1;
1415         maxz = z + fabs(dir[2]) * 0.1;
1416         minz = bound(mins[2], minz, maxs[2]);
1417         maxz = bound(mins[2], maxz, maxs[2]);
1418
1419         count = (int)(count * cl_particles_quality.value);
1420
1421         switch(type)
1422         {
1423         case 0:
1424                 count *= 4; // ick, this should be in the mod or maps?
1425
1426                 while(count--)
1427                 {
1428                         k = particlepalette[colorbase + (rand()&3)];
1429                         if (gamemode == GAME_GOODVSBAD2)
1430                                 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);
1431                         else
1432                                 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);
1433                 }
1434                 break;
1435         case 1:
1436                 while(count--)
1437                 {
1438                         k = particlepalette[colorbase + (rand()&3)];
1439                         if (gamemode == GAME_GOODVSBAD2)
1440                                 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);
1441                         else
1442                                 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);
1443                         if (p)
1444                                 VectorCopy(p->vel, p->relativedirection);
1445                 }
1446                 break;
1447         default:
1448                 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1449         }
1450 }
1451
1452 /*
1453 ===============
1454 CL_MoveParticles
1455 ===============
1456 */
1457 void CL_MoveParticles (void)
1458 {
1459         particle_t *p;
1460         int i, maxparticle, j, a, content;
1461         float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
1462         particletype_t *decaltype, *bloodtype;
1463         int hitent;
1464         trace_t trace;
1465
1466         // LordHavoc: early out condition
1467         if (!cl.num_particles)
1468         {
1469                 cl.free_particle = 0;
1470                 return;
1471         }
1472
1473         frametime = bound(0, cl.time - cl.oldtime, 0.1);
1474         gravity = frametime * sv_gravity.value;
1475         dvel = 1+4*frametime;
1476         decalfade = frametime * 255 / cl_decals_fadetime.value;
1477         decaltype = particletype + pt_decal;
1478         bloodtype = particletype + pt_blood;
1479
1480         maxparticle = -1;
1481         j = 0;
1482         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1483         {
1484                 if (!p->type)
1485                 {
1486                         if (cl.free_particle > i)
1487                                 cl.free_particle = i;
1488                         continue;
1489                 }
1490                 maxparticle = i;
1491
1492                 // heavily optimized decal case
1493                 if (p->type == decaltype)
1494                 {
1495                         // FIXME: this has fairly wacky handling of alpha
1496                         if (cl.time > p->time2 + cl_decals_time.value)
1497                         {
1498                                 p->alpha -= decalfade;
1499                                 if (p->alpha <= 0)
1500                                 {
1501                                         p->type = NULL;
1502                                         if (cl.free_particle > i)
1503                                                 cl.free_particle = i;
1504                                         continue;
1505                                 }
1506                         }
1507                         if (p->owner)
1508                         {
1509                                 if (cl.entities[p->owner].render.model == p->ownermodel)
1510                                 {
1511                                         Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1512                                         Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1513                                 }
1514                                 else
1515                                 {
1516                                         p->type = NULL;
1517                                         if (cl.free_particle > i)
1518                                                 cl.free_particle = i;
1519                                 }
1520                         }
1521                         continue;
1522                 }
1523
1524                 content = 0;
1525
1526                 p->alpha -= p->alphafade * frametime;
1527
1528                 if (p->alpha <= 0)
1529                 {
1530                         p->type = NULL;
1531                         if (cl.free_particle > i)
1532                                 cl.free_particle = i;
1533                         continue;
1534                 }
1535
1536                 if (p->type->orientation != PARTICLE_BEAM)
1537                 {
1538                         VectorCopy(p->org, oldorg);
1539                         VectorMA(p->org, frametime, p->vel, p->org);
1540                         VectorCopy(p->org, org);
1541                         if (p->bounce)
1542                         {
1543                                 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);
1544                                 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1545                                 // or if the trace hit something flagged as NOIMPACT
1546                                 // then remove the particle
1547                                 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1548                                 {
1549                                         p->type = NULL;
1550                                         continue;
1551                                 }
1552                                 // react if the particle hit something
1553                                 if (trace.fraction < 1)
1554                                 {
1555                                         VectorCopy(trace.endpos, p->org);
1556                                         if (p->type == particletype + pt_rain)
1557                                         {
1558                                                 // raindrop - splash on solid/water/slime/lava
1559                                                 int count;
1560                                                 // convert from a raindrop particle to a rainsplash decal
1561                                                 VectorCopy(trace.plane.normal, p->vel);
1562                                                 VectorAdd(p->org, p->vel, p->org);
1563                                                 p->type = particletype + pt_raindecal;
1564                                                 p->texnum = tex_rainsplash;
1565                                                 p->time2 = cl.time;
1566                                                 p->alphafade = p->alpha / 0.4;
1567                                                 p->bounce = 0;
1568                                                 p->airfriction = 0;
1569                                                 p->liquidfriction = 0;
1570                                                 p->gravity = 0;
1571                                                 p->size *= 1.0f;
1572                                                 p->sizeincrease = p->size * 16;
1573                                                 count = rand() & 3;
1574                                                 while(count--)
1575                                                         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);
1576                                         }
1577                                         else if (p->type == bloodtype)
1578                                         {
1579                                                 // blood - splash on solid
1580                                                 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1581                                                 {
1582                                                         p->type = NULL;
1583                                                         continue;
1584                                                 }
1585                                                 if (!cl_decals.integer)
1586                                                 {
1587                                                         p->type = NULL;
1588                                                         continue;
1589                                                 }
1590                                                 // convert from a blood particle to a blood decal
1591                                                 VectorCopy(trace.plane.normal, p->vel);
1592                                                 VectorAdd(p->org, p->vel, p->org);
1593                                                 if (cl_stainmaps.integer)
1594                                                         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)));
1595
1596                                                 p->type = particletype + pt_decal;
1597                                                 p->texnum = tex_blooddecal[rand()&7];
1598                                                 p->owner = hitent;
1599                                                 p->ownermodel = cl.entities[hitent].render.model;
1600                                                 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1601                                                 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1602                                                 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1603                                                 p->time2 = cl.time;
1604                                                 p->alphafade = 0;
1605                                                 p->bounce = 0;
1606                                                 p->airfriction = 0;
1607                                                 p->liquidfriction = 0;
1608                                                 p->gravity = 0;
1609                                                 p->size *= 2.0f;
1610                                         }
1611                                         else if (p->bounce < 0)
1612                                         {
1613                                                 // bounce -1 means remove on impact
1614                                                 p->type = NULL;
1615                                                 continue;
1616                                         }
1617                                         else
1618                                         {
1619                                                 // anything else - bounce off solid
1620                                                 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1621                                                 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1622                                                 if (DotProduct(p->vel, p->vel) < 0.03)
1623                                                         VectorClear(p->vel);
1624                                         }
1625                                 }
1626                         }
1627                         p->vel[2] -= p->gravity * gravity;
1628
1629                         if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1630                         {
1631                                 f = 1.0f - min(p->liquidfriction * frametime, 1);
1632                                 VectorScale(p->vel, f, p->vel);
1633                         }
1634                         else if (p->airfriction)
1635                         {
1636                                 f = 1.0f - min(p->airfriction * frametime, 1);
1637                                 VectorScale(p->vel, f, p->vel);
1638                         }
1639                 }
1640
1641                 if (p->type != particletype + pt_static)
1642                 {
1643                         switch (p->type - particletype)
1644                         {
1645                         case pt_entityparticle:
1646                                 // particle that removes itself after one rendered frame
1647                                 if (p->time2)
1648                                         p->type = NULL;
1649                                 else
1650                                         p->time2 = 1;
1651                                 break;
1652                         case pt_blood:
1653                                 a = CL_PointSuperContents(p->org);
1654                                 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1655                                 {
1656                                         p->size += frametime * 8;
1657                                         //p->alpha -= bloodwaterfade;
1658                                 }
1659                                 else
1660                                         p->vel[2] -= gravity;
1661                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1662                                         p->type = NULL;
1663                                 break;
1664                         case pt_bubble:
1665                                 a = CL_PointSuperContents(p->org);
1666                                 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1667                                 {
1668                                         p->type = NULL;
1669                                         break;
1670                                 }
1671                                 break;
1672                         case pt_rain:
1673                                 a = CL_PointSuperContents(p->org);
1674                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1675                                         p->type = NULL;
1676                                 break;
1677                         case pt_snow:
1678                                 if (cl.time > p->time2)
1679                                 {
1680                                         // snow flutter
1681                                         p->time2 = cl.time + (rand() & 3) * 0.1;
1682                                         p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1683                                         p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1684                                         //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1685                                 }
1686                                 a = CL_PointSuperContents(p->org);
1687                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1688                                         p->type = NULL;
1689                                 break;
1690                         default:
1691                                 break;
1692                         }
1693                 }
1694         }
1695         cl.num_particles = maxparticle + 1;
1696 }
1697
1698 #define MAX_PARTICLETEXTURES 64
1699 // particletexture_t is a rectangle in the particlefonttexture
1700 typedef struct particletexture_s
1701 {
1702         rtexture_t *texture;
1703         float s1, t1, s2, t2;
1704 }
1705 particletexture_t;
1706
1707 static rtexturepool_t *particletexturepool;
1708 static rtexture_t *particlefonttexture;
1709 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1710
1711 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1712
1713 #define PARTICLETEXTURESIZE 64
1714 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1715
1716 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1717 {
1718         float dz, f, dot;
1719         vec3_t normal;
1720         dz = 1 - (dx*dx+dy*dy);
1721         if (dz > 0) // it does hit the sphere
1722         {
1723                 f = 0;
1724                 // back side
1725                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1726                 VectorNormalize(normal);
1727                 dot = DotProduct(normal, light);
1728                 if (dot > 0.5) // interior reflection
1729                         f += ((dot *  2) - 1);
1730                 else if (dot < -0.5) // exterior reflection
1731                         f += ((dot * -2) - 1);
1732                 // front side
1733                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1734                 VectorNormalize(normal);
1735                 dot = DotProduct(normal, light);
1736                 if (dot > 0.5) // interior reflection
1737                         f += ((dot *  2) - 1);
1738                 else if (dot < -0.5) // exterior reflection
1739                         f += ((dot * -2) - 1);
1740                 f *= 128;
1741                 f += 16; // just to give it a haze so you can see the outline
1742                 f = bound(0, f, 255);
1743                 return (unsigned char) f;
1744         }
1745         else
1746                 return 0;
1747 }
1748
1749 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1750 {
1751         int basex, basey, y;
1752         basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1753         basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1754         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1755                 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1756 }
1757
1758 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1759 {
1760         int x, y;
1761         float cx, cy, dx, dy, f, iradius;
1762         unsigned char *d;
1763         cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1764         cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1765         iradius = 1.0f / radius;
1766         alpha *= (1.0f / 255.0f);
1767         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1768         {
1769                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1770                 {
1771                         dx = (x - cx);
1772                         dy = (y - cy);
1773                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1774                         if (f > 0)
1775                         {
1776                                 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1777                                 d[0] += (int)(f * (red   - d[0]));
1778                                 d[1] += (int)(f * (green - d[1]));
1779                                 d[2] += (int)(f * (blue  - d[2]));
1780                         }
1781                 }
1782         }
1783 }
1784
1785 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1786 {
1787         int i;
1788         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1789         {
1790                 data[0] = bound(minr, data[0], maxr);
1791                 data[1] = bound(ming, data[1], maxg);
1792                 data[2] = bound(minb, data[2], maxb);
1793         }
1794 }
1795
1796 void particletextureinvert(unsigned char *data)
1797 {
1798         int i;
1799         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1800         {
1801                 data[0] = 255 - data[0];
1802                 data[1] = 255 - data[1];
1803                 data[2] = 255 - data[2];
1804         }
1805 }
1806
1807 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1808 static void R_InitBloodTextures (unsigned char *particletexturedata)
1809 {
1810         int i, j, k, m;
1811         unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1812
1813         // blood particles
1814         for (i = 0;i < 8;i++)
1815         {
1816                 memset(&data[0][0][0], 255, sizeof(data));
1817                 for (k = 0;k < 24;k++)
1818                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1819                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1820                 particletextureinvert(&data[0][0][0]);
1821                 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1822         }
1823
1824         // blood decals
1825         for (i = 0;i < 8;i++)
1826         {
1827                 memset(&data[0][0][0], 255, sizeof(data));
1828                 m = 8;
1829                 for (j = 1;j < 10;j++)
1830                         for (k = min(j, m - 1);k < m;k++)
1831                                 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1832                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1833                 particletextureinvert(&data[0][0][0]);
1834                 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1835         }
1836
1837 }
1838
1839 //uncomment this to make engine save out particle font to a tga file when run
1840 //#define DUMPPARTICLEFONT
1841
1842 static void R_InitParticleTexture (void)
1843 {
1844         int x, y, d, i, k, m;
1845         float dx, dy, f;
1846         vec3_t light;
1847
1848         // a note: decals need to modulate (multiply) the background color to
1849         // properly darken it (stain), and they need to be able to alpha fade,
1850         // this is a very difficult challenge because it means fading to white
1851         // (no change to background) rather than black (darkening everything
1852         // behind the whole decal polygon), and to accomplish this the texture is
1853         // inverted (dark red blood on white background becomes brilliant cyan
1854         // and white on black background) so we can alpha fade it to black, then
1855         // we invert it again during the blendfunc to make it work...
1856
1857 #ifndef DUMPPARTICLEFONT
1858         particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1859         if (!particlefonttexture)
1860 #endif
1861         {
1862                 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1863                 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1864                 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1865
1866                 // smoke
1867                 for (i = 0;i < 8;i++)
1868                 {
1869                         memset(&data[0][0][0], 255, sizeof(data));
1870                         do
1871                         {
1872                                 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1873
1874                                 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1875                                 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1876                                 m = 0;
1877                                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1878                                 {
1879                                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1880                                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1881                                         {
1882                                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1883                                                 d = (noise2[y][x] - 128) * 3 + 192;
1884                                                 if (d > 0)
1885                                                         d = (int)(d * (1-(dx*dx+dy*dy)));
1886                                                 d = (d * noise1[y][x]) >> 7;
1887                                                 d = bound(0, d, 255);
1888                                                 data[y][x][3] = (unsigned char) d;
1889                                                 if (m < d)
1890                                                         m = d;
1891                                         }
1892                                 }
1893                         }
1894                         while (m < 224);
1895                         setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1896                 }
1897
1898                 // rain splash
1899                 memset(&data[0][0][0], 255, sizeof(data));
1900                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1901                 {
1902                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1903                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1904                         {
1905                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1906                                 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1907                                 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1908                         }
1909                 }
1910                 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1911
1912                 // normal particle
1913                 memset(&data[0][0][0], 255, sizeof(data));
1914                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1915                 {
1916                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1917                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1918                         {
1919                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1920                                 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1921                                 d = bound(0, d, 255);
1922                                 data[y][x][3] = (unsigned char) d;
1923                         }
1924                 }
1925                 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1926
1927                 // rain
1928                 memset(&data[0][0][0], 255, sizeof(data));
1929                 light[0] = 1;light[1] = 1;light[2] = 1;
1930                 VectorNormalize(light);
1931                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1932                 {
1933                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1934                         // stretch upper half of bubble by +50% and shrink lower half by -50%
1935                         // (this gives an elongated teardrop shape)
1936                         if (dy > 0.5f)
1937                                 dy = (dy - 0.5f) * 2.0f;
1938                         else
1939                                 dy = (dy - 0.5f) / 1.5f;
1940                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1941                         {
1942                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1943                                 // shrink bubble width to half
1944                                 dx *= 2.0f;
1945                                 data[y][x][3] = shadebubble(dx, dy, light);
1946                         }
1947                 }
1948                 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1949
1950                 // bubble
1951                 memset(&data[0][0][0], 255, sizeof(data));
1952                 light[0] = 1;light[1] = 1;light[2] = 1;
1953                 VectorNormalize(light);
1954                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1955                 {
1956                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1957                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1958                         {
1959                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1960                                 data[y][x][3] = shadebubble(dx, dy, light);
1961                         }
1962                 }
1963                 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1964
1965                 // Blood particles and blood decals
1966                 R_InitBloodTextures (particletexturedata);
1967
1968                 // bullet decals
1969                 for (i = 0;i < 8;i++)
1970                 {
1971                         memset(&data[0][0][0], 255, sizeof(data));
1972                         for (k = 0;k < 12;k++)
1973                                 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1974                         for (k = 0;k < 3;k++)
1975                                 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1976                         //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1977                         particletextureinvert(&data[0][0][0]);
1978                         setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1979                 }
1980
1981 #ifdef DUMPPARTICLEFONT
1982                 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1983 #endif
1984
1985                 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1986
1987                 Mem_Free(particletexturedata);
1988         }
1989         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1990         {
1991                 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1992                 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1993                 particletexture[i].texture = particlefonttexture;
1994                 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1995                 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1996                 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1997                 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1998         }
1999
2000 #ifndef DUMPPARTICLEFONT
2001         particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
2002         if (!particletexture[tex_beam].texture)
2003 #endif
2004         {
2005                 unsigned char noise3[64][64], data2[64][16][4];
2006                 // nexbeam
2007                 fractalnoise(&noise3[0][0], 64, 4);
2008                 m = 0;
2009                 for (y = 0;y < 64;y++)
2010                 {
2011                         dy = (y - 0.5f*64) / (64*0.5f-1);
2012                         for (x = 0;x < 16;x++)
2013                         {
2014                                 dx = (x - 0.5f*16) / (16*0.5f-2);
2015                                 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2016                                 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2017                                 data2[y][x][3] = 255;
2018                         }
2019                 }
2020
2021 #ifdef DUMPPARTICLEFONT
2022                 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2023 #endif
2024                 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2025         }
2026         particletexture[tex_beam].s1 = 0;
2027         particletexture[tex_beam].t1 = 0;
2028         particletexture[tex_beam].s2 = 1;
2029         particletexture[tex_beam].t2 = 1;
2030 }
2031
2032 static void r_part_start(void)
2033 {
2034         particletexturepool = R_AllocTexturePool();
2035         R_InitParticleTexture ();
2036         CL_Particles_LoadEffectInfo();
2037 }
2038
2039 static void r_part_shutdown(void)
2040 {
2041         R_FreeTexturePool(&particletexturepool);
2042 }
2043
2044 static void r_part_newmap(void)
2045 {
2046 }
2047
2048 #define BATCHSIZE 256
2049 int particle_element3i[BATCHSIZE*6];
2050 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2051
2052 void R_Particles_Init (void)
2053 {
2054         int i;
2055         for (i = 0;i < BATCHSIZE;i++)
2056         {
2057                 particle_element3i[i*6+0] = i*4+0;
2058                 particle_element3i[i*6+1] = i*4+1;
2059                 particle_element3i[i*6+2] = i*4+2;
2060                 particle_element3i[i*6+3] = i*4+0;
2061                 particle_element3i[i*6+4] = i*4+2;
2062                 particle_element3i[i*6+5] = i*4+3;
2063         }
2064
2065         Cvar_RegisterVariable(&r_drawparticles);
2066         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2067 }
2068
2069 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2070 {
2071         int surfacelistindex;
2072         int batchstart, batchcount;
2073         const particle_t *p;
2074         pblend_t blendmode;
2075         rtexture_t *texture;
2076         float *v3f, *t2f, *c4f;
2077
2078         R_Mesh_Matrix(&identitymatrix);
2079         R_Mesh_ResetTextureState();
2080         R_Mesh_VertexPointer(particle_vertex3f);
2081         R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f);
2082         R_Mesh_ColorPointer(particle_color4f);
2083         GL_DepthMask(false);
2084         GL_DepthTest(true);
2085         GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
2086
2087         // first generate all the vertices at once
2088         for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2089         {
2090                 particletexture_t *tex;
2091                 const float *org;
2092                 float up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
2093
2094                 p = cl.particles + surfacelist[surfacelistindex];
2095
2096                 blendmode = p->type->blendmode;
2097
2098                 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2099                 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2100                 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2101                 ca = p->alpha * (1.0f / 255.0f);
2102                 if (blendmode == PBLEND_MOD)
2103                 {
2104                         cr *= ca;
2105                         cg *= ca;
2106                         cb *= ca;
2107                         cr = min(cr, 1);
2108                         cg = min(cg, 1);
2109                         cb = min(cb, 1);
2110                         ca = 1;
2111                 }
2112                 ca /= cl_particles_quality.value;
2113                 if (p->type->lighting)
2114                 {
2115                         float ambient[3], diffuse[3], diffusenormal[3];
2116                         R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2117                         cr *= (ambient[0] + 0.5 * diffuse[0]);
2118                         cg *= (ambient[1] + 0.5 * diffuse[1]);
2119                         cb *= (ambient[2] + 0.5 * diffuse[2]);
2120                 }
2121                 if (r_refdef.fogenabled)
2122                 {
2123                         fog = VERTEXFOGTABLE(VectorDistance(p->org, r_view.origin));
2124                         ifog = 1 - fog;
2125                         cr = cr * ifog;
2126                         cg = cg * ifog;
2127                         cb = cb * ifog;
2128                         if (blendmode == PBLEND_ALPHA)
2129                         {
2130                                 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2131                                 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2132                                 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2133                         }
2134                 }
2135                 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2136                 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2137                 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2138                 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2139
2140                 size = p->size * cl_particles_size.value;
2141                 org = p->org;
2142                 tex = &particletexture[p->texnum];
2143                 if (p->type->orientation == PARTICLE_BILLBOARD)
2144                 {
2145                         VectorScale(r_view.left, -size, right);
2146                         VectorScale(r_view.up, size, up);
2147                         v3f[ 0] = org[0] - right[0] - up[0];
2148                         v3f[ 1] = org[1] - right[1] - up[1];
2149                         v3f[ 2] = org[2] - right[2] - up[2];
2150                         v3f[ 3] = org[0] - right[0] + up[0];
2151                         v3f[ 4] = org[1] - right[1] + up[1];
2152                         v3f[ 5] = org[2] - right[2] + up[2];
2153                         v3f[ 6] = org[0] + right[0] + up[0];
2154                         v3f[ 7] = org[1] + right[1] + up[1];
2155                         v3f[ 8] = org[2] + right[2] + up[2];
2156                         v3f[ 9] = org[0] + right[0] - up[0];
2157                         v3f[10] = org[1] + right[1] - up[1];
2158                         v3f[11] = org[2] + right[2] - up[2];
2159                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2160                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2161                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2162                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2163                 }
2164                 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2165                 {
2166                         // double-sided
2167                         if (DotProduct(p->vel, r_view.origin) > DotProduct(p->vel, org))
2168                         {
2169                                 VectorNegate(p->vel, v);
2170                                 VectorVectors(v, right, up);
2171                         }
2172                         else
2173                                 VectorVectors(p->vel, right, up);
2174                         VectorScale(right, size, right);
2175                         VectorScale(up, size, up);
2176                         v3f[ 0] = org[0] - right[0] - up[0];
2177                         v3f[ 1] = org[1] - right[1] - up[1];
2178                         v3f[ 2] = org[2] - right[2] - up[2];
2179                         v3f[ 3] = org[0] - right[0] + up[0];
2180                         v3f[ 4] = org[1] - right[1] + up[1];
2181                         v3f[ 5] = org[2] - right[2] + up[2];
2182                         v3f[ 6] = org[0] + right[0] + up[0];
2183                         v3f[ 7] = org[1] + right[1] + up[1];
2184                         v3f[ 8] = org[2] + right[2] + up[2];
2185                         v3f[ 9] = org[0] + right[0] - up[0];
2186                         v3f[10] = org[1] + right[1] - up[1];
2187                         v3f[11] = org[2] + right[2] - up[2];
2188                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2189                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2190                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2191                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2192                 }
2193                 else if (p->type->orientation == PARTICLE_SPARK)
2194                 {
2195                         VectorMA(org, -0.02, p->vel, v);
2196                         VectorMA(org, 0.02, p->vel, up2);
2197                         R_CalcBeam_Vertex3f(v3f, v, up2, size);
2198                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2199                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2200                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2201                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2202                 }
2203                 else if (p->type->orientation == PARTICLE_BEAM)
2204                 {
2205                         R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2206                         VectorSubtract(p->vel, org, up);
2207                         VectorNormalize(up);
2208                         v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2209                         v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2210                         t2f[0] = 1;t2f[1] = v[0];
2211                         t2f[2] = 0;t2f[3] = v[0];
2212                         t2f[4] = 0;t2f[5] = v[1];
2213                         t2f[6] = 1;t2f[7] = v[1];
2214                 }
2215                 else
2216                 {
2217                         Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2218                         return;
2219                 }
2220         }
2221
2222         // now render batches of particles based on blendmode and texture
2223         blendmode = PBLEND_ADD;
2224         GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2225         texture = particletexture[63].texture;
2226         R_Mesh_TexBind(0, R_GetTexture(texture));
2227         GL_LockArrays(0, numsurfaces*4);
2228         batchstart = 0;
2229         batchcount = 0;
2230         for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2231         {
2232                 p = cl.particles + surfacelist[surfacelistindex];
2233
2234                 if (blendmode != p->type->blendmode)
2235                 {
2236                         if (batchcount > 0)
2237                                 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2238                         batchcount = 0;
2239                         batchstart = surfacelistindex;
2240                         blendmode = p->type->blendmode;
2241                         if (blendmode == PBLEND_ALPHA)
2242                                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2243                         else if (blendmode == PBLEND_ADD)
2244                                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2245                         else //if (blendmode == PBLEND_MOD)
2246                                 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2247                 }
2248                 if (texture != particletexture[p->texnum].texture)
2249                 {
2250                         if (batchcount > 0)
2251                                 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2252                         batchcount = 0;
2253                         batchstart = surfacelistindex;
2254                         texture = particletexture[p->texnum].texture;
2255                         R_Mesh_TexBind(0, R_GetTexture(texture));
2256                 }
2257
2258                 batchcount++;
2259         }
2260         if (batchcount > 0)
2261                 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2262         GL_LockArrays(0, 0);
2263 }
2264
2265 void R_DrawParticles (void)
2266 {
2267         int i;
2268         float minparticledist;
2269         particle_t *p;
2270
2271         // LordHavoc: early out conditions
2272         if ((!cl.num_particles) || (!r_drawparticles.integer))
2273                 return;
2274
2275         minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2276
2277         // LordHavoc: only render if not too close
2278         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2279         {
2280                 if (p->type)
2281                 {
2282                         r_refdef.stats.particles++;
2283                         if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2284                                 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2285                 }
2286         }
2287 }
2288