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