2 Copyright (C) 1996-1997 Id Software, Inc.
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.
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.
13 See the GNU General Public License for more details.
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.
23 #include "cl_collision.h"
27 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
33 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
38 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
51 typedef struct particleeffectinfo_s
53 int effectnameindex; // which effect this belongs to
54 // PARTICLEEFFECT_* bits
56 // blood effects may spawn very few particles, so proper fraction-overflow
57 // handling is very important, this variable keeps track of the fraction
58 double particleaccumulator;
59 // the math is: countabsolute + requestedcount * countmultiplier * quality
60 // absolute number of particles to spawn, often used for decals
61 // (unaffected by quality and requestedcount)
63 // multiplier for the number of particles CL_ParticleEffect was told to
64 // spawn, most effects do not really have a count and hence use 1, so
65 // this is often the actual count to spawn, not merely a multiplier
66 float countmultiplier;
67 // if > 0 this causes the particle to spawn in an evenly spaced line from
68 // originmins to originmaxs (causing them to describe a trail, not a box)
70 // type of particle to spawn (defines some aspects of behavior)
72 // blending mode used on this particle type
74 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
75 porientation_t orientation;
76 // range of colors to choose from in hex RRGGBB (like HTML color tags),
77 // randomly interpolated at spawn
78 unsigned int color[2];
79 // a random texture is chosen in this range (note the second value is one
80 // past the last choosable, so for example 8,16 chooses any from 8 up and
82 // if start and end of the range are the same, no randomization is done
84 // range of size values randomly chosen when spawning, plus size increase over time
86 // range of alpha values randomly chosen when spawning, plus alpha fade
88 // how long the particle should live (note it is also removed if alpha drops to 0)
90 // how much gravity affects this particle (negative makes it fly up!)
92 // how much bounce the particle has when it hits a surface
93 // if negative the particle is removed on impact
95 // if in air this friction is applied
96 // if negative the particle accelerates
98 // if in liquid (water/slime/lava) this friction is applied
99 // if negative the particle accelerates
100 float liquidfriction;
101 // these offsets are added to the values given to particleeffect(), and
102 // then an ellipsoid-shaped jitter is added as defined by these
103 // (they are the 3 radii)
105 // stretch velocity factor (used for sparks)
106 float originoffset[3];
107 float velocityoffset[3];
108 float originjitter[3];
109 float velocityjitter[3];
110 float velocitymultiplier;
111 // an effect can also spawn a dlight
112 float lightradiusstart;
113 float lightradiusfade;
116 qboolean lightshadow;
118 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
121 particleeffectinfo_t;
123 #define MAX_PARTICLEEFFECTNAME 256
124 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 #define MAX_PARTICLEEFFECTINFO 4096
128 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
130 static int particlepalette[256];
132 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
133 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
134 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
135 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
136 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
137 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
138 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
139 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
140 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
141 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
142 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
143 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
144 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
145 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
146 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
147 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
148 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
149 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
150 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
151 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
152 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
153 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
154 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
155 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
156 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
157 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
158 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
159 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
160 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
161 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
162 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
163 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
166 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
167 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
168 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
170 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
172 // texture numbers in particle font
173 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
174 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
175 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
176 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
177 static const int tex_rainsplash = 32;
178 static const int tex_particle = 63;
179 static const int tex_bubble = 62;
180 static const int tex_raindrop = 61;
181 static const int tex_beam = 60;
183 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
184 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
185 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
186 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
187 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
188 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
189 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
190 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
191 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
192 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
193 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
194 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
195 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
196 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
197 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
198 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
199 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
200 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
201 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
202 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
203 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
204 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
205 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
208 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
214 particleeffectinfo_t *info = NULL;
215 const char *text = textstart;
217 effectinfoindex = -1;
218 for (linenumber = 1;;linenumber++)
221 for (arrayindex = 0;arrayindex < 16;arrayindex++)
222 argv[arrayindex][0] = 0;
225 if (!COM_ParseToken_Simple(&text, true, false))
227 if (!strcmp(com_token, "\n"))
231 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
237 #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;}
238 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
239 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
240 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
241 #define readfloat(var) checkparms(2);var = atof(argv[1])
242 if (!strcmp(argv[0], "effect"))
247 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
249 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
252 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
254 if (particleeffectname[effectnameindex][0])
256 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
261 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
265 // if we run out of names, abort
266 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
268 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
271 info = particleeffectinfo + effectinfoindex;
272 info->effectnameindex = effectnameindex;
273 info->particletype = pt_alphastatic;
274 info->blendmode = particletype[info->particletype].blendmode;
275 info->orientation = particletype[info->particletype].orientation;
276 info->tex[0] = tex_particle;
277 info->tex[1] = tex_particle;
278 info->color[0] = 0xFFFFFF;
279 info->color[1] = 0xFFFFFF;
283 info->alpha[1] = 256;
284 info->alpha[2] = 256;
285 info->time[0] = 9999;
286 info->time[1] = 9999;
287 VectorSet(info->lightcolor, 1, 1, 1);
288 info->lightshadow = true;
289 info->lighttime = 9999;
290 info->stretchfactor = 1;
291 info->staincolor[0] = -1;
292 info->staincolor[1] = -1;
293 info->staintex[0] = -1;
294 info->staintex[1] = -1;
296 else if (info == NULL)
298 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
301 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
302 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
303 else if (!strcmp(argv[0], "type"))
306 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
307 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
308 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
309 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
310 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
311 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
312 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
313 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
314 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
315 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
316 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
317 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
318 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
319 info->blendmode = particletype[info->particletype].blendmode;
320 info->orientation = particletype[info->particletype].orientation;
322 else if (!strcmp(argv[0], "blend"))
325 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
326 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
327 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
328 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
330 else if (!strcmp(argv[0], "orientation"))
333 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
334 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
335 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
336 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_BEAM;
337 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
339 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
340 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
341 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
342 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
343 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
344 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
345 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
346 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
347 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
348 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
349 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
350 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
351 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
352 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
353 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
354 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
355 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
356 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
357 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
358 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
359 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
360 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
361 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
362 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
363 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
364 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
365 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
366 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = -1; info->staincolor[1] = -1;}
368 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
377 int CL_ParticleEffectIndexForName(const char *name)
380 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
381 if (!strcmp(particleeffectname[i], name))
386 const char *CL_ParticleEffectNameForIndex(int i)
388 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
390 return particleeffectname[i];
393 // MUST match effectnameindex_t in client.h
394 static const char *standardeffectnames[EFFECT_TOTAL] =
418 "TE_TEI_BIGEXPLOSION",
434 void CL_Particles_LoadEffectInfo(void)
437 unsigned char *filedata;
438 fs_offset_t filesize;
439 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
440 memset(particleeffectname, 0, sizeof(particleeffectname));
441 for (i = 0;i < EFFECT_TOTAL;i++)
442 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
443 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
446 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
456 void CL_ReadPointFile_f (void);
457 void CL_Particles_Init (void)
459 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)");
460 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
462 Cvar_RegisterVariable (&cl_particles);
463 Cvar_RegisterVariable (&cl_particles_quality);
464 Cvar_RegisterVariable (&cl_particles_alpha);
465 Cvar_RegisterVariable (&cl_particles_size);
466 Cvar_RegisterVariable (&cl_particles_quake);
467 Cvar_RegisterVariable (&cl_particles_blood);
468 Cvar_RegisterVariable (&cl_particles_blood_alpha);
469 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
470 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
471 Cvar_RegisterVariable (&cl_particles_explosions_shell);
472 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
473 Cvar_RegisterVariable (&cl_particles_rain);
474 Cvar_RegisterVariable (&cl_particles_snow);
475 Cvar_RegisterVariable (&cl_particles_smoke);
476 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
477 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
478 Cvar_RegisterVariable (&cl_particles_sparks);
479 Cvar_RegisterVariable (&cl_particles_bubbles);
480 Cvar_RegisterVariable (&cl_particles_visculling);
481 Cvar_RegisterVariable (&cl_decals);
482 Cvar_RegisterVariable (&cl_decals_visculling);
483 Cvar_RegisterVariable (&cl_decals_time);
484 Cvar_RegisterVariable (&cl_decals_fadetime);
487 void CL_Particles_Shutdown (void)
491 // list of all 26 parameters:
492 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
493 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
494 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
495 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
496 // palpha - opacity of particle as 0-255 (can be more than 255)
497 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
498 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
499 // pgravity - how much effect gravity has on the particle (0-1)
500 // 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
501 // px,py,pz - starting origin of particle
502 // pvx,pvy,pvz - starting velocity of particle
503 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
504 // blendmode - one of the PBLEND_ values
505 // orientation - one of the PARTICLE_ values
506 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
507 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
508 static particle_t *CL_NewParticle(unsigned short ptypeindex, 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, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex)
513 if (!cl_particles.integer)
515 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
516 if (cl.free_particle >= cl.max_particles)
519 lifetime = palpha / min(1, palphafade);
520 part = &cl.particles[cl.free_particle++];
521 if (cl.num_particles < cl.free_particle)
522 cl.num_particles = cl.free_particle;
523 memset(part, 0, sizeof(*part));
524 part->typeindex = ptypeindex;
525 part->blendmode = blendmode;
526 part->orientation = orientation;
527 l2 = (int)lhrandom(0.5, 256.5);
529 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
530 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
531 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
532 part->staintexnum = staintex;
533 if(staincolor1 >= 0 && staincolor2 >= 0)
535 l2 = (int)lhrandom(0.5, 256.5);
537 if(blendmode == PBLEND_INVMOD)
539 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
540 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
541 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
545 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
546 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
547 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
549 if(r > 0xFF) r = 0xFF;
550 if(g > 0xFF) g = 0xFF;
551 if(b > 0xFF) b = 0xFF;
555 r = part->color[0]; // -1 is shorthand for stain = particle color
559 part->staincolor = r * 65536 + g * 256 + b;
562 part->sizeincrease = psizeincrease;
563 part->alpha = palpha;
564 part->alphafade = palphafade;
565 part->gravity = pgravity;
566 part->bounce = pbounce;
567 part->stretch = stretch;
569 part->org[0] = px + originjitter * v[0];
570 part->org[1] = py + originjitter * v[1];
571 part->org[2] = pz + originjitter * v[2];
572 part->vel[0] = pvx + velocityjitter * v[0];
573 part->vel[1] = pvy + velocityjitter * v[1];
574 part->vel[2] = pvz + velocityjitter * v[2];
576 part->airfriction = pairfriction;
577 part->liquidfriction = pliquidfriction;
578 part->die = cl.time + lifetime;
579 part->delayedcollisions = 0;
580 part->qualityreduction = pqualityreduction;
581 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
582 if (part->typeindex == pt_rain)
586 float lifetime = part->die - cl.time;
589 // turn raindrop into simple spark and create delayedspawn splash effect
590 part->typeindex = pt_spark;
592 VectorMA(part->org, lifetime, part->vel, endvec);
593 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
594 part->die = cl.time + lifetime * trace.fraction;
595 part2 = CL_NewParticle(pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1);
598 part2->delayedspawn = part->die;
599 part2->die += part->die - cl.time;
600 for (i = rand() & 7;i < 10;i++)
602 part2 = CL_NewParticle(pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
605 part2->delayedspawn = part->die;
606 part2->die += part->die - cl.time;
611 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
613 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
616 VectorMA(part->org, lifetime, part->vel, endvec);
617 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
618 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
623 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
627 if (!cl_decals.integer)
629 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
630 if (cl.free_decal >= cl.max_decals)
632 decal = &cl.decals[cl.free_decal++];
633 if (cl.num_decals < cl.free_decal)
634 cl.num_decals = cl.free_decal;
635 memset(decal, 0, sizeof(*decal));
636 decal->typeindex = pt_decal;
637 decal->texnum = texnum;
638 VectorAdd(org, normal, decal->org);
639 VectorCopy(normal, decal->normal);
641 decal->alpha = alpha;
642 decal->time2 = cl.time;
643 l2 = (int)lhrandom(0.5, 256.5);
645 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
646 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
647 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
648 decal->owner = hitent;
649 decal->clusterindex = -1000; // no vis culling unless we're sure
652 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
653 decal->ownermodel = cl.entities[decal->owner].render.model;
654 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
655 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
659 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
661 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
663 decal->clusterindex = leaf->clusterindex;
668 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
671 float bestfrac, bestorg[3], bestnormal[3];
673 int besthitent = 0, hitent;
676 for (i = 0;i < 32;i++)
679 VectorMA(org, maxdist, org2, org2);
680 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
681 // take the closest trace result that doesn't end up hitting a NOMARKS
682 // surface (sky for example)
683 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
685 bestfrac = trace.fraction;
687 VectorCopy(trace.endpos, bestorg);
688 VectorCopy(trace.plane.normal, bestnormal);
692 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
695 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
696 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
697 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)
700 matrix4x4_t tempmatrix;
701 VectorLerp(originmins, 0.5, originmaxs, center);
702 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
703 if (effectnameindex == EFFECT_SVC_PARTICLE)
705 if (cl_particles.integer)
707 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
709 CL_ParticleExplosion(center);
710 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
711 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
714 count *= cl_particles_quality.value;
715 for (;count > 0;count--)
717 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
718 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 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, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
723 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
724 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
725 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
726 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
727 else if (effectnameindex == EFFECT_TE_SPIKE)
729 if (cl_particles_bulletimpacts.integer)
731 if (cl_particles_quake.integer)
733 if (cl_particles_smoke.integer)
734 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
738 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
739 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
740 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
744 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
745 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
747 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
749 if (cl_particles_bulletimpacts.integer)
751 if (cl_particles_quake.integer)
753 if (cl_particles_smoke.integer)
754 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
758 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
759 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
760 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
764 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
765 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
766 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);
768 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
770 if (cl_particles_bulletimpacts.integer)
772 if (cl_particles_quake.integer)
774 if (cl_particles_smoke.integer)
775 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
779 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
780 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
781 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
785 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
786 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
788 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
790 if (cl_particles_bulletimpacts.integer)
792 if (cl_particles_quake.integer)
794 if (cl_particles_smoke.integer)
795 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
799 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
800 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
801 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
805 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
806 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
807 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);
809 else if (effectnameindex == EFFECT_TE_BLOOD)
811 if (!cl_particles_blood.integer)
813 if (cl_particles_quake.integer)
814 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
817 static double bloodaccumulator = 0;
818 //CL_NewParticle(pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
819 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
820 for (;bloodaccumulator > 0;bloodaccumulator--)
821 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 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, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
824 else if (effectnameindex == EFFECT_TE_SPARK)
825 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
826 else if (effectnameindex == EFFECT_TE_PLASMABURN)
828 // plasma scorch mark
829 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
830 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
831 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
833 else if (effectnameindex == EFFECT_TE_GUNSHOT)
835 if (cl_particles_bulletimpacts.integer)
837 if (cl_particles_quake.integer)
838 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
841 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
842 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
843 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
847 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
848 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
850 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
852 if (cl_particles_bulletimpacts.integer)
854 if (cl_particles_quake.integer)
855 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
858 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
859 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
860 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
864 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
865 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
866 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);
868 else if (effectnameindex == EFFECT_TE_EXPLOSION)
870 CL_ParticleExplosion(center);
871 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);
873 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
875 CL_ParticleExplosion(center);
876 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);
878 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
880 if (cl_particles_quake.integer)
883 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
886 CL_NewParticle(pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
888 CL_NewParticle(pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
892 CL_ParticleExplosion(center);
893 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);
895 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
896 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);
897 else if (effectnameindex == EFFECT_TE_FLAMEJET)
899 count *= cl_particles_quality.value;
901 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
903 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
905 float i, j, inc, vel;
908 inc = 8 / cl_particles_quality.value;
909 for (i = -128;i < 128;i += inc)
911 for (j = -128;j < 128;j += inc)
913 dir[0] = j + lhrandom(0, inc);
914 dir[1] = i + lhrandom(0, inc);
916 org[0] = center[0] + dir[0];
917 org[1] = center[1] + dir[1];
918 org[2] = center[2] + lhrandom(0, 64);
919 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
920 CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
924 else if (effectnameindex == EFFECT_TE_TELEPORT)
926 float i, j, k, inc, vel;
929 if (cl_particles_quake.integer)
930 inc = 4 / cl_particles_quality.value;
932 inc = 8 / cl_particles_quality.value;
933 for (i = -16;i < 16;i += inc)
935 for (j = -16;j < 16;j += inc)
937 for (k = -24;k < 32;k += inc)
939 VectorSet(dir, i*8, j*8, k*8);
940 VectorNormalize(dir);
941 vel = lhrandom(50, 113);
942 if (cl_particles_quake.integer)
943 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 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, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
945 CL_NewParticle(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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
949 if (!cl_particles_quake.integer)
950 CL_NewParticle(pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
951 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);
953 else if (effectnameindex == EFFECT_TE_TEI_G3)
954 CL_NewParticle(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, false, 0, 1, PBLEND_ADD, PARTICLE_BEAM, -1, -1, -1);
955 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
957 if (cl_particles_smoke.integer)
959 count *= 0.25f * cl_particles_quality.value;
961 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
964 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
966 CL_ParticleExplosion(center);
967 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);
969 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
972 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
973 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
974 if (cl_particles_smoke.integer)
975 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
976 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
977 if (cl_particles_sparks.integer)
978 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
979 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
980 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);
982 else if (effectnameindex == EFFECT_EF_FLAME)
984 count *= 300 * cl_particles_quality.value;
986 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
987 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);
989 else if (effectnameindex == EFFECT_EF_STARDUST)
991 count *= 200 * cl_particles_quality.value;
993 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
994 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);
996 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1000 int smoke, blood, bubbles, r, color;
1002 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1005 Vector4Set(light, 0, 0, 0, 0);
1007 if (effectnameindex == EFFECT_TR_ROCKET)
1008 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1009 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1011 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1012 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1014 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1016 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1017 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1021 matrix4x4_t tempmatrix;
1022 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1023 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1024 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1028 if (!spawnparticles)
1031 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1034 VectorSubtract(originmaxs, originmins, dir);
1035 len = VectorNormalizeLength(dir);
1038 dec = -ent->persistent.trail_time;
1039 ent->persistent.trail_time += len;
1040 if (ent->persistent.trail_time < 0.01f)
1043 // if we skip out, leave it reset
1044 ent->persistent.trail_time = 0.0f;
1049 // advance into this frame to reach the first puff location
1050 VectorMA(originmins, dec, dir, pos);
1053 smoke = cl_particles.integer && cl_particles_smoke.integer;
1054 blood = cl_particles.integer && cl_particles_blood.integer;
1055 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1056 qd = 1.0f / cl_particles_quality.value;
1063 if (effectnameindex == EFFECT_TR_BLOOD)
1065 if (cl_particles_quake.integer)
1067 color = particlepalette[67 + (rand()&3)];
1068 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1073 CL_NewParticle(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, 1, -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, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
1076 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1078 if (cl_particles_quake.integer)
1081 color = particlepalette[67 + (rand()&3)];
1082 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1087 CL_NewParticle(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, 1, -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, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
1093 if (effectnameindex == EFFECT_TR_ROCKET)
1095 if (cl_particles_quake.integer)
1098 color = particlepalette[ramp3[r]];
1099 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1103 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1104 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1107 else if (effectnameindex == EFFECT_TR_GRENADE)
1109 if (cl_particles_quake.integer)
1112 color = particlepalette[ramp3[r]];
1113 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1117 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1120 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1122 if (cl_particles_quake.integer)
1125 color = particlepalette[52 + (rand()&7)];
1126 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1127 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1129 else if (gamemode == GAME_GOODVSBAD2)
1132 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1136 color = particlepalette[20 + (rand()&7)];
1137 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1140 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1142 if (cl_particles_quake.integer)
1145 color = particlepalette[230 + (rand()&7)];
1146 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1147 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1151 color = particlepalette[226 + (rand()&7)];
1152 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1155 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1157 if (cl_particles_quake.integer)
1159 color = particlepalette[152 + (rand()&3)];
1160 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1162 else if (gamemode == GAME_GOODVSBAD2)
1165 CL_NewParticle(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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1167 else if (gamemode == GAME_PRYDON)
1170 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1173 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1175 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1178 CL_NewParticle(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, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1180 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1183 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1185 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1186 CL_NewParticle(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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1190 if (effectnameindex == EFFECT_TR_ROCKET)
1191 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1192 else if (effectnameindex == EFFECT_TR_GRENADE)
1193 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1195 // advance to next time and position
1198 VectorMA (pos, dec, dir, pos);
1201 ent->persistent.trail_time = len;
1203 else if (developer.integer >= 1)
1204 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1207 // this is also called on point effects with spawndlight = true and
1208 // spawnparticles = true
1209 // it is called CL_ParticleTrail because most code does not want to supply
1210 // these parameters, only trail handling does
1211 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)
1214 qboolean found = false;
1215 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1217 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1218 return; // no such effect
1220 VectorLerp(originmins, 0.5, originmaxs, center);
1221 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1223 int effectinfoindex;
1226 particleeffectinfo_t *info;
1228 vec3_t centervelocity;
1234 qboolean underwater;
1235 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1236 VectorLerp(originmins, 0.5, originmaxs, center);
1237 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1238 supercontents = CL_PointSuperContents(center);
1239 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1240 VectorSubtract(originmaxs, originmins, traildir);
1241 traillen = VectorLength(traildir);
1242 VectorNormalize(traildir);
1243 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1245 if (info->effectnameindex == effectnameindex)
1248 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1250 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1253 // spawn a dlight if requested
1254 if (info->lightradiusstart > 0 && spawndlight)
1256 matrix4x4_t tempmatrix;
1257 if (info->trailspacing > 0)
1258 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1260 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1261 if (info->lighttime > 0 && info->lightradiusfade > 0)
1263 // light flash (explosion, etc)
1264 // called when effect starts
1265 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);
1270 // called by CL_LinkNetworkEntity
1271 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1272 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.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);
1273 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1277 if (!spawnparticles)
1282 if (info->tex[1] > info->tex[0])
1284 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1285 tex = min(tex, info->tex[1] - 1);
1287 if(info->staintex[0] < 0)
1288 staintex = info->staintex[0];
1291 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1292 staintex = min(staintex, info->staintex[1] - 1);
1294 if (info->particletype == pt_decal)
1295 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]);
1296 else if (info->orientation == PARTICLE_BEAM)
1297 CL_NewParticle(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, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1300 if (!cl_particles.integer)
1302 switch (info->particletype)
1304 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1305 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1306 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1307 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1308 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1309 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1312 VectorCopy(originmins, trailpos);
1313 if (info->trailspacing > 0)
1315 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1316 trailstep = info->trailspacing / cl_particles_quality.value;
1320 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1323 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1324 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1326 if (info->tex[1] > info->tex[0])
1328 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1329 tex = min(tex, info->tex[1] - 1);
1333 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1334 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1335 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1338 CL_NewParticle(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, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1340 VectorMA(trailpos, trailstep, traildir, trailpos);
1347 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1350 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)
1352 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1360 void CL_EntityParticles (const entity_t *ent)
1363 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1364 static vec3_t avelocities[NUMVERTEXNORMALS];
1365 if (!cl_particles.integer) return;
1366 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1368 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1370 if (!avelocities[0][0])
1371 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1372 avelocities[0][i] = lhrandom(0, 2.55);
1374 for (i = 0;i < NUMVERTEXNORMALS;i++)
1376 yaw = cl.time * avelocities[i][0];
1377 pitch = cl.time * avelocities[i][1];
1378 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1379 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1380 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1381 CL_NewParticle(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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1386 void CL_ReadPointFile_f (void)
1388 vec3_t org, leakorg;
1390 char *pointfile = NULL, *pointfilepos, *t, tchar;
1391 char name[MAX_OSPATH];
1396 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1397 strlcat (name, ".pts", sizeof (name));
1398 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1401 Con_Printf("Could not open %s\n", name);
1405 Con_Printf("Reading %s...\n", name);
1406 VectorClear(leakorg);
1409 pointfilepos = pointfile;
1410 while (*pointfilepos)
1412 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1417 while (*t && *t != '\n' && *t != '\r')
1421 #if _MSC_VER >= 1400
1422 #define sscanf sscanf_s
1424 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1430 VectorCopy(org, leakorg);
1433 if (cl.num_particles < cl.max_particles - 3)
1436 CL_NewParticle(pt_alphastatic, 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, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1439 Mem_Free(pointfile);
1440 VectorCopy(leakorg, org);
1441 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1443 CL_NewParticle(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, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM, -1, -1, -1);
1444 CL_NewParticle(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, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM, -1, -1, -1);
1445 CL_NewParticle(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, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM, -1, -1, -1);
1450 CL_ParseParticleEffect
1452 Parse an effect out of the server message
1455 void CL_ParseParticleEffect (void)
1458 int i, count, msgcount, color;
1460 MSG_ReadVector(org, cls.protocol);
1461 for (i=0 ; i<3 ; i++)
1462 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1463 msgcount = MSG_ReadByte ();
1464 color = MSG_ReadByte ();
1466 if (msgcount == 255)
1471 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1476 CL_ParticleExplosion
1480 void CL_ParticleExplosion (const vec3_t org)
1486 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1487 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1489 if (cl_particles_quake.integer)
1491 for (i = 0;i < 1024;i++)
1497 color = particlepalette[ramp1[r]];
1498 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1502 color = particlepalette[ramp2[r]];
1503 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1509 i = CL_PointSuperContents(org);
1510 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1512 if (cl_particles.integer && cl_particles_bubbles.integer)
1513 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1514 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1518 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1520 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1524 for (k = 0;k < 16;k++)
1527 VectorMA(org, 128, v2, v);
1528 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1529 if (trace.fraction >= 0.1)
1532 VectorSubtract(trace.endpos, org, v2);
1533 VectorScale(v2, 2.0f, v2);
1534 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1540 if (cl_particles_explosions_shell.integer)
1541 R_NewExplosion(org);
1546 CL_ParticleExplosion2
1550 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1553 if (!cl_particles.integer) return;
1555 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1557 k = particlepalette[colorStart + (i % colorLength)];
1558 if (cl_particles_quake.integer)
1559 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1561 CL_NewParticle(pt_alphastatic, 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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1565 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1567 if (cl_particles_sparks.integer)
1569 sparkcount *= cl_particles_quality.value;
1570 while(sparkcount-- > 0)
1571 CL_NewParticle(pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1575 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1577 if (cl_particles_smoke.integer)
1579 smokecount *= cl_particles_quality.value;
1580 while(smokecount-- > 0)
1581 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1585 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)
1588 if (!cl_particles.integer) return;
1590 count = (int)(count * cl_particles_quality.value);
1593 k = particlepalette[colorbase + (rand()&3)];
1594 CL_NewParticle(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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1598 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1601 float minz, maxz, lifetime = 30;
1602 if (!cl_particles.integer) return;
1603 if (dir[2] < 0) // falling
1605 minz = maxs[2] + dir[2] * 0.1;
1608 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1613 maxz = maxs[2] + dir[2] * 0.1;
1615 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1618 count = (int)(count * cl_particles_quality.value);
1623 if (!cl_particles_rain.integer) break;
1624 count *= 4; // ick, this should be in the mod or maps?
1628 k = particlepalette[colorbase + (rand()&3)];
1629 if (gamemode == GAME_GOODVSBAD2)
1630 CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 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, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1632 CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 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, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1636 if (!cl_particles_snow.integer) break;
1639 k = particlepalette[colorbase + (rand()&3)];
1640 if (gamemode == GAME_GOODVSBAD2)
1641 CL_NewParticle(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, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1643 CL_NewParticle(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, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1647 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1651 #define MAX_PARTICLETEXTURES 64
1652 // particletexture_t is a rectangle in the particlefonttexture
1653 typedef struct particletexture_s
1655 rtexture_t *texture;
1656 float s1, t1, s2, t2;
1660 static rtexturepool_t *particletexturepool;
1661 static rtexture_t *particlefonttexture;
1662 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1664 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1665 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1666 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1667 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1669 #define PARTICLETEXTURESIZE 64
1670 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1672 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1676 dz = 1 - (dx*dx+dy*dy);
1677 if (dz > 0) // it does hit the sphere
1681 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1682 VectorNormalize(normal);
1683 dot = DotProduct(normal, light);
1684 if (dot > 0.5) // interior reflection
1685 f += ((dot * 2) - 1);
1686 else if (dot < -0.5) // exterior reflection
1687 f += ((dot * -2) - 1);
1689 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1690 VectorNormalize(normal);
1691 dot = DotProduct(normal, light);
1692 if (dot > 0.5) // interior reflection
1693 f += ((dot * 2) - 1);
1694 else if (dot < -0.5) // exterior reflection
1695 f += ((dot * -2) - 1);
1697 f += 16; // just to give it a haze so you can see the outline
1698 f = bound(0, f, 255);
1699 return (unsigned char) f;
1705 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1707 int basex, basey, y;
1708 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1709 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1710 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1711 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1714 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1717 float cx, cy, dx, dy, f, iradius;
1719 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1720 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1721 iradius = 1.0f / radius;
1722 alpha *= (1.0f / 255.0f);
1723 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1725 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1729 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1734 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1735 d[0] += (int)(f * (blue - d[0]));
1736 d[1] += (int)(f * (green - d[1]));
1737 d[2] += (int)(f * (red - d[2]));
1743 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1746 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1748 data[0] = bound(minb, data[0], maxb);
1749 data[1] = bound(ming, data[1], maxg);
1750 data[2] = bound(minr, data[2], maxr);
1754 void particletextureinvert(unsigned char *data)
1757 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1759 data[0] = 255 - data[0];
1760 data[1] = 255 - data[1];
1761 data[2] = 255 - data[2];
1765 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1766 static void R_InitBloodTextures (unsigned char *particletexturedata)
1769 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1772 for (i = 0;i < 8;i++)
1774 memset(&data[0][0][0], 255, sizeof(data));
1775 for (k = 0;k < 24;k++)
1776 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1777 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1778 particletextureinvert(&data[0][0][0]);
1779 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1783 for (i = 0;i < 8;i++)
1785 memset(&data[0][0][0], 255, sizeof(data));
1787 for (j = 1;j < 10;j++)
1788 for (k = min(j, m - 1);k < m;k++)
1789 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1790 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1791 particletextureinvert(&data[0][0][0]);
1792 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1797 //uncomment this to make engine save out particle font to a tga file when run
1798 //#define DUMPPARTICLEFONT
1800 static void R_InitParticleTexture (void)
1802 int x, y, d, i, k, m;
1806 // a note: decals need to modulate (multiply) the background color to
1807 // properly darken it (stain), and they need to be able to alpha fade,
1808 // this is a very difficult challenge because it means fading to white
1809 // (no change to background) rather than black (darkening everything
1810 // behind the whole decal polygon), and to accomplish this the texture is
1811 // inverted (dark red blood on white background becomes brilliant cyan
1812 // and white on black background) so we can alpha fade it to black, then
1813 // we invert it again during the blendfunc to make it work...
1815 #ifndef DUMPPARTICLEFONT
1816 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1817 if (!particlefonttexture)
1820 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1821 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1822 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1825 for (i = 0;i < 8;i++)
1827 memset(&data[0][0][0], 255, sizeof(data));
1830 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1832 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1833 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1835 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1837 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1838 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1840 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1841 d = (noise2[y][x] - 128) * 3 + 192;
1843 d = (int)(d * (1-(dx*dx+dy*dy)));
1844 d = (d * noise1[y][x]) >> 7;
1845 d = bound(0, d, 255);
1846 data[y][x][3] = (unsigned char) d;
1853 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1857 memset(&data[0][0][0], 255, sizeof(data));
1858 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1860 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1861 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1863 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1864 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1865 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1868 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1871 memset(&data[0][0][0], 255, sizeof(data));
1872 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1874 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1875 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1877 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1878 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1879 d = bound(0, d, 255);
1880 data[y][x][3] = (unsigned char) d;
1883 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1886 memset(&data[0][0][0], 255, sizeof(data));
1887 light[0] = 1;light[1] = 1;light[2] = 1;
1888 VectorNormalize(light);
1889 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1891 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1892 // stretch upper half of bubble by +50% and shrink lower half by -50%
1893 // (this gives an elongated teardrop shape)
1895 dy = (dy - 0.5f) * 2.0f;
1897 dy = (dy - 0.5f) / 1.5f;
1898 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1900 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1901 // shrink bubble width to half
1903 data[y][x][3] = shadebubble(dx, dy, light);
1906 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1909 memset(&data[0][0][0], 255, sizeof(data));
1910 light[0] = 1;light[1] = 1;light[2] = 1;
1911 VectorNormalize(light);
1912 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1914 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1915 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1917 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1918 data[y][x][3] = shadebubble(dx, dy, light);
1921 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1923 // Blood particles and blood decals
1924 R_InitBloodTextures (particletexturedata);
1927 for (i = 0;i < 8;i++)
1929 memset(&data[0][0][0], 255, sizeof(data));
1930 for (k = 0;k < 12;k++)
1931 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1932 for (k = 0;k < 3;k++)
1933 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1934 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1935 particletextureinvert(&data[0][0][0]);
1936 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1939 #ifdef DUMPPARTICLEFONT
1940 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1943 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1945 Mem_Free(particletexturedata);
1947 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1949 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1950 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1951 particletexture[i].texture = particlefonttexture;
1952 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1953 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1954 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1955 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1958 #ifndef DUMPPARTICLEFONT
1959 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1960 if (!particletexture[tex_beam].texture)
1963 unsigned char noise3[64][64], data2[64][16][4];
1965 fractalnoise(&noise3[0][0], 64, 4);
1967 for (y = 0;y < 64;y++)
1969 dy = (y - 0.5f*64) / (64*0.5f-1);
1970 for (x = 0;x < 16;x++)
1972 dx = (x - 0.5f*16) / (16*0.5f-2);
1973 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1974 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1975 data2[y][x][3] = 255;
1979 #ifdef DUMPPARTICLEFONT
1980 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1982 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1984 particletexture[tex_beam].s1 = 0;
1985 particletexture[tex_beam].t1 = 0;
1986 particletexture[tex_beam].s2 = 1;
1987 particletexture[tex_beam].t2 = 1;
1990 static void r_part_start(void)
1993 // generate particlepalette for convenience from the main one
1994 for (i = 0;i < 256;i++)
1995 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
1996 particletexturepool = R_AllocTexturePool();
1997 R_InitParticleTexture ();
1998 CL_Particles_LoadEffectInfo();
2001 static void r_part_shutdown(void)
2003 R_FreeTexturePool(&particletexturepool);
2006 static void r_part_newmap(void)
2008 CL_Particles_LoadEffectInfo();
2011 #define BATCHSIZE 256
2012 unsigned short particle_elements[BATCHSIZE*6];
2014 void R_Particles_Init (void)
2017 for (i = 0;i < BATCHSIZE;i++)
2019 particle_elements[i*6+0] = i*4+0;
2020 particle_elements[i*6+1] = i*4+1;
2021 particle_elements[i*6+2] = i*4+2;
2022 particle_elements[i*6+3] = i*4+0;
2023 particle_elements[i*6+4] = i*4+2;
2024 particle_elements[i*6+5] = i*4+3;
2027 Cvar_RegisterVariable(&r_drawparticles);
2028 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2029 Cvar_RegisterVariable(&r_drawdecals);
2030 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2031 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2034 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2036 int surfacelistindex;
2038 float *v3f, *t2f, *c4f;
2039 particletexture_t *tex;
2040 float right[3], up[3], size, ca;
2041 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
2042 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2044 r_refdef.stats.decals += numsurfaces;
2045 R_Mesh_Matrix(&identitymatrix);
2046 R_Mesh_ResetTextureState();
2047 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2048 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2049 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2050 R_SetupGenericShader(true);
2051 GL_DepthMask(false);
2052 GL_DepthRange(0, 1);
2053 GL_PolygonOffset(0, 0);
2055 GL_CullFace(GL_NONE);
2057 // generate all the vertices at once
2058 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2060 d = cl.decals + surfacelist[surfacelistindex];
2063 c4f = particle_color4f + 16*surfacelistindex;
2064 ca = d->alpha * alphascale;
2065 if (r_refdef.fogenabled)
2066 ca *= FogPoint_World(d->org);
2067 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2068 Vector4Copy(c4f, c4f + 4);
2069 Vector4Copy(c4f, c4f + 8);
2070 Vector4Copy(c4f, c4f + 12);
2072 // calculate vertex positions
2073 size = d->size * cl_particles_size.value;
2074 VectorVectors(d->normal, right, up);
2075 VectorScale(right, size, right);
2076 VectorScale(up, size, up);
2077 v3f = particle_vertex3f + 12*surfacelistindex;
2078 v3f[ 0] = d->org[0] - right[0] - up[0];
2079 v3f[ 1] = d->org[1] - right[1] - up[1];
2080 v3f[ 2] = d->org[2] - right[2] - up[2];
2081 v3f[ 3] = d->org[0] - right[0] + up[0];
2082 v3f[ 4] = d->org[1] - right[1] + up[1];
2083 v3f[ 5] = d->org[2] - right[2] + up[2];
2084 v3f[ 6] = d->org[0] + right[0] + up[0];
2085 v3f[ 7] = d->org[1] + right[1] + up[1];
2086 v3f[ 8] = d->org[2] + right[2] + up[2];
2087 v3f[ 9] = d->org[0] + right[0] - up[0];
2088 v3f[10] = d->org[1] + right[1] - up[1];
2089 v3f[11] = d->org[2] + right[2] - up[2];
2091 // calculate texcoords
2092 tex = &particletexture[d->texnum];
2093 t2f = particle_texcoord2f + 8*surfacelistindex;
2094 t2f[0] = tex->s1;t2f[1] = tex->t2;
2095 t2f[2] = tex->s1;t2f[3] = tex->t1;
2096 t2f[4] = tex->s2;t2f[5] = tex->t1;
2097 t2f[6] = tex->s2;t2f[7] = tex->t2;
2100 // now render the decals all at once
2101 // (this assumes they all use one particle font texture!)
2102 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2103 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2104 GL_LockArrays(0, numsurfaces*4);
2105 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2106 GL_LockArrays(0, 0);
2109 void R_DrawDecals (void)
2117 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2118 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2120 // LordHavoc: early out conditions
2121 if ((!cl.num_decals) || (!r_drawdecals.integer))
2124 decalfade = frametime * 256 / cl_decals_fadetime.value;
2125 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2126 drawdist2 = drawdist2*drawdist2;
2128 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2130 if (!decal->typeindex)
2133 if (cl.time > decal->time2 + cl_decals_time.value)
2135 decal->alpha -= decalfade;
2136 if (decal->alpha <= 0)
2142 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2144 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2145 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2151 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2154 if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size))
2155 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2158 decal->typeindex = 0;
2159 if (cl.free_decal > i)
2163 // reduce cl.num_decals if possible
2164 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2167 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2169 decal_t *olddecals = cl.decals;
2170 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2171 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2172 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2173 Mem_Free(olddecals);
2177 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2179 int surfacelistindex;
2180 int batchstart, batchcount;
2181 const particle_t *p;
2183 rtexture_t *texture;
2184 float *v3f, *t2f, *c4f;
2185 particletexture_t *tex;
2186 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2187 float ambient[3], diffuse[3], diffusenormal[3];
2188 vec4_t colormultiplier;
2189 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2191 Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f));
2193 r_refdef.stats.particles += numsurfaces;
2194 R_Mesh_Matrix(&identitymatrix);
2195 R_Mesh_ResetTextureState();
2196 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2197 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2198 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2199 R_SetupGenericShader(true);
2200 GL_DepthMask(false);
2201 GL_DepthRange(0, 1);
2202 GL_PolygonOffset(0, 0);
2204 GL_CullFace(GL_NONE);
2206 // first generate all the vertices at once
2207 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2209 p = cl.particles + surfacelist[surfacelistindex];
2211 blendmode = p->blendmode;
2213 c4f[0] = p->color[0] * colormultiplier[0];
2214 c4f[1] = p->color[1] * colormultiplier[1];
2215 c4f[2] = p->color[2] * colormultiplier[2];
2216 c4f[3] = p->alpha * colormultiplier[3];
2219 case PBLEND_INVALID:
2222 // additive and modulate can just fade out in fog (this is correct)
2223 if (r_refdef.fogenabled)
2224 c4f[3] *= FogPoint_World(p->org);
2225 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2232 // note: lighting is not cheap!
2233 if (particletype[p->typeindex].lighting)
2235 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2236 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2237 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2238 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2240 // mix in the fog color
2241 if (r_refdef.fogenabled)
2243 fog = FogPoint_World(p->org);
2245 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2246 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2247 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2251 // copy the color into the other three vertices
2252 Vector4Copy(c4f, c4f + 4);
2253 Vector4Copy(c4f, c4f + 8);
2254 Vector4Copy(c4f, c4f + 12);
2256 size = p->size * cl_particles_size.value;
2257 tex = &particletexture[p->texnum];
2258 switch(p->orientation)
2260 case PARTICLE_INVALID:
2261 case PARTICLE_BILLBOARD:
2262 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2263 VectorScale(r_refdef.view.up, size, up);
2264 v3f[ 0] = p->org[0] - right[0] - up[0];
2265 v3f[ 1] = p->org[1] - right[1] - up[1];
2266 v3f[ 2] = p->org[2] - right[2] - up[2];
2267 v3f[ 3] = p->org[0] - right[0] + up[0];
2268 v3f[ 4] = p->org[1] - right[1] + up[1];
2269 v3f[ 5] = p->org[2] - right[2] + up[2];
2270 v3f[ 6] = p->org[0] + right[0] + up[0];
2271 v3f[ 7] = p->org[1] + right[1] + up[1];
2272 v3f[ 8] = p->org[2] + right[2] + up[2];
2273 v3f[ 9] = p->org[0] + right[0] - up[0];
2274 v3f[10] = p->org[1] + right[1] - up[1];
2275 v3f[11] = p->org[2] + right[2] - up[2];
2276 t2f[0] = tex->s1;t2f[1] = tex->t2;
2277 t2f[2] = tex->s1;t2f[3] = tex->t1;
2278 t2f[4] = tex->s2;t2f[5] = tex->t1;
2279 t2f[6] = tex->s2;t2f[7] = tex->t2;
2281 case PARTICLE_ORIENTED_DOUBLESIDED:
2282 VectorVectors(p->vel, right, up);
2283 VectorScale(right, size * p->stretch, right);
2284 VectorScale(up, size, up);
2285 v3f[ 0] = p->org[0] - right[0] - up[0];
2286 v3f[ 1] = p->org[1] - right[1] - up[1];
2287 v3f[ 2] = p->org[2] - right[2] - up[2];
2288 v3f[ 3] = p->org[0] - right[0] + up[0];
2289 v3f[ 4] = p->org[1] - right[1] + up[1];
2290 v3f[ 5] = p->org[2] - right[2] + up[2];
2291 v3f[ 6] = p->org[0] + right[0] + up[0];
2292 v3f[ 7] = p->org[1] + right[1] + up[1];
2293 v3f[ 8] = p->org[2] + right[2] + up[2];
2294 v3f[ 9] = p->org[0] + right[0] - up[0];
2295 v3f[10] = p->org[1] + right[1] - up[1];
2296 v3f[11] = p->org[2] + right[2] - up[2];
2297 t2f[0] = tex->s1;t2f[1] = tex->t2;
2298 t2f[2] = tex->s1;t2f[3] = tex->t1;
2299 t2f[4] = tex->s2;t2f[5] = tex->t1;
2300 t2f[6] = tex->s2;t2f[7] = tex->t2;
2302 case PARTICLE_SPARK:
2303 len = VectorLength(p->vel);
2304 VectorNormalize2(p->vel, up);
2305 lenfactor = p->stretch * 0.04 * len;
2306 if(lenfactor < size * 0.5)
2307 lenfactor = size * 0.5;
2308 VectorMA(p->org, -lenfactor, up, v);
2309 VectorMA(p->org, lenfactor, up, up2);
2310 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2311 t2f[0] = tex->s1;t2f[1] = tex->t2;
2312 t2f[2] = tex->s1;t2f[3] = tex->t1;
2313 t2f[4] = tex->s2;t2f[5] = tex->t1;
2314 t2f[6] = tex->s2;t2f[7] = tex->t2;
2317 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2318 VectorSubtract(p->vel, p->org, up);
2319 VectorNormalize(up);
2320 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2321 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2322 t2f[0] = 1;t2f[1] = v[0];
2323 t2f[2] = 0;t2f[3] = v[0];
2324 t2f[4] = 0;t2f[5] = v[1];
2325 t2f[6] = 1;t2f[7] = v[1];
2330 // now render batches of particles based on blendmode and texture
2331 blendmode = PBLEND_INVALID;
2333 GL_LockArrays(0, numsurfaces*4);
2336 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2338 p = cl.particles + surfacelist[surfacelistindex];
2340 if (blendmode != p->blendmode)
2342 blendmode = p->blendmode;
2346 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2348 case PBLEND_INVALID:
2350 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2353 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2357 if (texture != particletexture[p->texnum].texture)
2359 texture = particletexture[p->texnum].texture;
2360 R_Mesh_TexBind(0, R_GetTexture(texture));
2363 // iterate until we find a change in settings
2364 batchstart = surfacelistindex++;
2365 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2367 p = cl.particles + surfacelist[surfacelistindex];
2368 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2372 batchcount = surfacelistindex - batchstart;
2373 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2375 GL_LockArrays(0, 0);
2378 void R_DrawParticles (void)
2381 float minparticledist;
2383 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2389 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2390 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2392 // LordHavoc: early out conditions
2393 if ((!cl.num_particles) || (!r_drawparticles.integer))
2396 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2397 gravity = frametime * cl.movevars_gravity;
2398 dvel = 1+4*frametime;
2399 decalfade = frametime * 255 / cl_decals_fadetime.value;
2400 update = frametime > 0;
2401 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2402 drawdist2 = drawdist2*drawdist2;
2404 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2408 if (cl.free_particle > i)
2409 cl.free_particle = i;
2415 if (p->delayedspawn > cl.time)
2417 p->delayedspawn = 0;
2421 p->size += p->sizeincrease * frametime;
2422 p->alpha -= p->alphafade * frametime;
2424 if (p->alpha <= 0 || p->die <= cl.time)
2427 if (p->orientation != PARTICLE_BEAM && frametime > 0)
2429 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2431 if (p->typeindex == pt_blood)
2432 p->size += frametime * 8;
2434 p->vel[2] -= p->gravity * gravity;
2435 f = 1.0f - min(p->liquidfriction * frametime, 1);
2436 VectorScale(p->vel, f, p->vel);
2440 p->vel[2] -= p->gravity * gravity;
2443 f = 1.0f - min(p->airfriction * frametime, 1);
2444 VectorScale(p->vel, f, p->vel);
2448 VectorCopy(p->org, oldorg);
2449 VectorMA(p->org, frametime, p->vel, p->org);
2450 if (p->bounce && cl.time >= p->delayedcollisions)
2452 trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
2453 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2454 // or if the trace hit something flagged as NOIMPACT
2455 // then remove the particle
2456 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2458 VectorCopy(trace.endpos, p->org);
2459 // react if the particle hit something
2460 if (trace.fraction < 1)
2462 VectorCopy(trace.endpos, p->org);
2464 if (p->staintexnum >= 0)
2466 // blood - splash on solid
2467 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2470 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2471 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2472 if (cl_decals.integer)
2474 // create a decal for the blood splat
2475 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, 0xFFFFFF ^ p->staincolor, 0xFFFFFF ^ p->staincolor, p->staintexnum, p->size * 2, p->alpha); // staincolor needs to be inverted for decals!
2480 if (p->typeindex == pt_blood)
2482 // blood - splash on solid
2483 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2485 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2487 R_Stain(p->org, 16, 64, 16, 16, (int)(p->alpha * p->size * (1.0f / 80.0f)), 64, 32, 32, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2488 if (cl_decals.integer)
2490 // create a decal for the blood splat
2491 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * 2, p->alpha);
2496 else if (p->bounce < 0)
2498 // bounce -1 means remove on impact
2503 // anything else - bounce off solid
2504 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2505 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2506 if (DotProduct(p->vel, p->vel) < 0.03)
2507 VectorClear(p->vel);
2513 if (p->typeindex != pt_static)
2515 switch (p->typeindex)
2517 case pt_entityparticle:
2518 // particle that removes itself after one rendered frame
2525 a = CL_PointSuperContents(p->org);
2526 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2530 a = CL_PointSuperContents(p->org);
2531 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2535 a = CL_PointSuperContents(p->org);
2536 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2540 if (cl.time > p->time2)
2543 p->time2 = cl.time + (rand() & 3) * 0.1;
2544 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2545 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2547 a = CL_PointSuperContents(p->org);
2548 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2556 else if (p->delayedspawn)
2559 // don't render particles too close to the view (they chew fillrate)
2560 // also don't render particles behind the view (useless)
2561 // further checks to cull to the frustum would be too slow here
2562 switch(p->typeindex)
2565 // beams have no culling
2566 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2569 if(cl_particles_visculling.integer)
2570 if (!r_refdef.viewcache.world_novis)
2571 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2573 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2575 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2578 // anything else just has to be in front of the viewer and visible at this distance
2579 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2580 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2587 if (cl.free_particle > i)
2588 cl.free_particle = i;
2591 // reduce cl.num_particles if possible
2592 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2595 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2597 particle_t *oldparticles = cl.particles;
2598 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2599 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2600 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2601 Mem_Free(oldparticles);