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