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