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