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