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