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