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"
26 // must match ptype_t values
27 particletype_t particletype[pt_total] =
29 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
30 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
31 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
32 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
34 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
36 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
37 {PBLEND_MOD, PARTICLE_BILLBOARD, false}, //pt_blood
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
39 {PBLEND_MOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
40 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
43 #define PARTICLEEFFECT_UNDERWATER 1
44 #define PARTICLEEFFECT_NOTUNDERWATER 2
46 typedef struct particleeffectinfo_s
48 int effectnameindex; // which effect this belongs to
49 // PARTICLEEFFECT_* bits
51 // blood effects may spawn very few particles, so proper fraction-overflow
52 // handling is very important, this variable keeps track of the fraction
53 double particleaccumulator;
54 // the math is: countabsolute + requestedcount * countmultiplier * quality
55 // absolute number of particles to spawn, often used for decals
56 // (unaffected by quality and requestedcount)
58 // multiplier for the number of particles CL_ParticleEffect was told to
59 // spawn, most effects do not really have a count and hence use 1, so
60 // this is often the actual count to spawn, not merely a multiplier
61 float countmultiplier;
62 // if > 0 this causes the particle to spawn in an evenly spaced line from
63 // originmins to originmaxs (causing them to describe a trail, not a box)
65 // type of particle to spawn (defines some aspects of behavior)
67 // range of colors to choose from in hex RRGGBB (like HTML color tags),
68 // randomly interpolated at spawn
69 unsigned int color[2];
70 // a random texture is chosen in this range (note the second value is one
71 // past the last choosable, so for example 8,16 chooses any from 8 up and
73 // if start and end of the range are the same, no randomization is done
75 // range of size values randomly chosen when spawning
77 // range of alpha values randomly chosen when spawning, plus alpha fade
79 // how long the particle should live (note it is also removed if alpha drops to 0)
81 // how much gravity affects this particle (negative makes it fly up!)
83 // how much bounce the particle has when it hits a surface
84 // if negative the particle is removed on impact
86 // if in air this friction is applied
87 // if negative the particle accelerates
89 // if in liquid (water/slime/lava) this friction is applied
90 // if negative the particle accelerates
92 // these offsets are added to the values given to particleeffect(), and
93 // then an ellipsoid-shaped jitter is added as defined by these
94 // (they are the 3 radii)
95 float originoffset[3];
96 float velocityoffset[3];
97 float originjitter[3];
98 float velocityjitter[3];
99 float velocitymultiplier;
100 // an effect can also spawn a dlight
101 float lightradiusstart;
102 float lightradiusfade;
105 qboolean lightshadow;
108 particleeffectinfo_t;
110 #define MAX_PARTICLEEFFECTNAME 256
111 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
113 #define MAX_PARTICLEEFFECTINFO 4096
115 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
117 static int particlepalette[256] =
119 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
120 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
121 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
122 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
123 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
124 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
125 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
126 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
127 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
128 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
129 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
130 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
131 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
132 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
133 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
134 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
135 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
136 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
137 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
138 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
139 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
140 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
141 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
142 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
143 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
144 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
145 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
146 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
147 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
148 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
149 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
150 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
153 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
154 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
155 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
157 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
159 // texture numbers in particle font
160 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
161 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
162 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
163 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
164 static const int tex_rainsplash[16] = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
165 static const int tex_particle = 63;
166 static const int tex_bubble = 62;
167 static const int tex_raindrop = 61;
168 static const int tex_beam = 60;
170 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
171 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles and reduces their alpha"};
172 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
173 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
174 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
175 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "0.5", "opacity of blood"};
176 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
177 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
178 cvar_t cl_particles_explosions_smoke = {CVAR_SAVE, "cl_particles_explosions_smokes", "0", "enables smoke from explosions"};
179 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
180 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
181 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
182 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
183 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
184 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
185 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
186 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "0", "enables decals (bullet holes, blood, etc)"};
187 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "0", "how long before decals start to fade away"};
188 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "20", "how long decals take to fade away"};
191 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
197 particleeffectinfo_t *info = NULL;
198 const char *text = textstart;
200 effectinfoindex = -1;
201 for (linenumber = 1;;linenumber++)
204 for (arrayindex = 0;arrayindex < 16;arrayindex++)
205 argv[arrayindex][0] = 0;
208 if (!COM_ParseToken(&text, true))
210 if (!strcmp(com_token, "\n"))
214 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
220 #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;}
221 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = (int)atof(argv[1+arrayindex])
222 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
223 #define readint(var) checkparms(2);var = (int)atof(argv[1])
224 #define readfloat(var) checkparms(2);var = atof(argv[1])
225 if (!strcmp(argv[0], "effect"))
230 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
232 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
235 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
237 if (particleeffectname[effectnameindex][0])
239 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
244 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
248 // if we run out of names, abort
249 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
251 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
254 info = particleeffectinfo + effectinfoindex;
255 info->effectnameindex = effectnameindex;
256 info->particletype = pt_alphastatic;
257 info->tex[0] = tex_particle;
258 info->tex[1] = tex_particle;
259 info->color[0] = 0xFFFFFF;
260 info->color[1] = 0xFFFFFF;
264 info->alpha[1] = 256;
265 info->alpha[2] = 256;
266 info->time[0] = 9999;
267 info->time[1] = 9999;
268 VectorSet(info->lightcolor, 1, 1, 1);
269 info->lightshadow = true;
270 info->lighttime = 9999;
272 else if (info == NULL)
274 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
277 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
278 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
279 else if (!strcmp(argv[0], "type"))
282 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
283 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
284 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
285 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
286 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
287 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
288 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
289 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
290 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
291 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
292 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
293 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
294 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
297 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
299 // LordHavoc: Black committed this without consulting with me, it breaks parsing of effectinfo.txt and thus I can't accept it
300 else if (!strcmp(argv[0], "color"))
302 unsigned color[6] = {0};
304 sscanf( argv[1], "%i %i %i", &color[0], &color[1], &color[2] );
305 sscanf( argv[2], "%i %i %i", &color[3], &color[4], &color[5] );
306 info->color[0] = color[0] + (color[1] << 8) + (color[2] << 16);
307 info->color[1] = color[3] + (color[4] << 8) + (color[5] << 16);
310 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
311 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
312 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
313 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
314 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
315 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
316 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
317 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
318 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
319 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
320 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
321 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
322 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
323 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
324 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
325 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
326 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
327 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
328 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
329 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
330 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
331 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
333 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
342 int CL_ParticleEffectIndexForName(const char *name)
345 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
346 if (!strcmp(particleeffectname[i], name))
351 const char *CL_ParticleEffectNameForIndex(int i)
353 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
355 return particleeffectname[i];
358 // MUST match effectnameindex_t in client.h
359 static const char *standardeffectnames[EFFECT_TOTAL] =
384 "TE_TEI_BIGEXPLOSION",
400 void CL_Particles_LoadEffectInfo(void)
403 unsigned char *filedata;
404 fs_offset_t filesize;
405 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
406 memset(particleeffectname, 0, sizeof(particleeffectname));
407 for (i = 0;i < EFFECT_TOTAL;i++)
408 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
409 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
412 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
422 void CL_ReadPointFile_f (void);
423 void CL_Particles_Init (void)
425 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)");
426 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
428 Cvar_RegisterVariable (&cl_particles);
429 Cvar_RegisterVariable (&cl_particles_quality);
430 Cvar_RegisterVariable (&cl_particles_size);
431 Cvar_RegisterVariable (&cl_particles_quake);
432 Cvar_RegisterVariable (&cl_particles_blood);
433 Cvar_RegisterVariable (&cl_particles_blood_alpha);
434 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
435 Cvar_RegisterVariable (&cl_particles_explosions_smoke);
436 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
437 Cvar_RegisterVariable (&cl_particles_explosions_shell);
438 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
439 Cvar_RegisterVariable (&cl_particles_smoke);
440 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
441 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
442 Cvar_RegisterVariable (&cl_particles_sparks);
443 Cvar_RegisterVariable (&cl_particles_bubbles);
444 Cvar_RegisterVariable (&cl_decals);
445 Cvar_RegisterVariable (&cl_decals_time);
446 Cvar_RegisterVariable (&cl_decals_fadetime);
449 void CL_Particles_Shutdown (void)
453 // list of all 26 parameters:
454 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
455 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
456 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
457 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
458 // palpha - opacity of particle as 0-255 (can be more than 255)
459 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
460 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
461 // pgravity - how much effect gravity has on the particle (0-1)
462 // 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
463 // px,py,pz - starting origin of particle
464 // pvx,pvy,pvz - starting velocity of particle
465 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
466 static particle_t *particle(particletype_t *ptype, int pcolor1, int pcolor2, int ptex, float psize, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pfriction, float originjitter, float velocityjitter)
471 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].type;cl.free_particle++);
472 if (cl.free_particle >= cl.max_particles)
474 part = &cl.particles[cl.free_particle++];
475 if (cl.num_particles < cl.free_particle)
476 cl.num_particles = cl.free_particle;
477 memset(part, 0, sizeof(*part));
479 l2 = (int)lhrandom(0.5, 256.5);
481 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
482 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
483 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
484 part->color[3] = 0xFF;
487 part->alpha = palpha;
488 part->alphafade = palphafade;
489 part->gravity = pgravity;
490 part->bounce = pbounce;
492 part->org[0] = px + originjitter * v[0];
493 part->org[1] = py + originjitter * v[1];
494 part->org[2] = pz + originjitter * v[2];
495 part->vel[0] = pvx + velocityjitter * v[0];
496 part->vel[1] = pvy + velocityjitter * v[1];
497 part->vel[2] = pvz + velocityjitter * v[2];
499 part->friction = pfriction;
503 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
506 if (!cl_decals.integer)
508 p = particle(particletype + pt_decal, color1, color2, texnum, size, alpha, 0, 0, 0, org[0] + normal[0], org[1] + normal[1], org[2] + normal[2], normal[0], normal[1], normal[2], 0, 0, 0);
513 p->ownermodel = cl.entities[p->owner].render.model;
514 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
515 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
516 VectorAdd(p->relativeorigin, p->relativedirection, p->relativeorigin);
520 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
523 float bestfrac, bestorg[3], bestnormal[3];
525 int besthitent = 0, hitent;
528 for (i = 0;i < 32;i++)
531 VectorMA(org, maxdist, org2, org2);
532 trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, false);
533 // take the closest trace result that doesn't end up hitting a NOMARKS
534 // surface (sky for example)
535 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
537 bestfrac = trace.fraction;
539 VectorCopy(trace.endpos, bestorg);
540 VectorCopy(trace.plane.normal, bestnormal);
544 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
547 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount);
548 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)
551 matrix4x4_t tempmatrix;
552 VectorLerp(originmins, 0.5, originmaxs, center);
553 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
554 if (effectnameindex == EFFECT_TE_WIZSPIKE)
555 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
556 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
557 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
558 else if (effectnameindex == EFFECT_TE_SPIKE)
560 if (cl_particles_bulletimpacts.integer)
562 if (cl_particles_quake.integer)
564 if (cl_particles_smoke.integer)
565 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
568 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
571 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
572 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
574 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
576 if (cl_particles_bulletimpacts.integer)
578 if (cl_particles_quake.integer)
580 if (cl_particles_smoke.integer)
581 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
584 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
587 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
588 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
589 CL_AllocDlight(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);
591 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
593 if (cl_particles_bulletimpacts.integer)
595 if (cl_particles_quake.integer)
597 if (cl_particles_smoke.integer)
598 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
601 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
604 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
605 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
607 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
609 if (cl_particles_bulletimpacts.integer)
611 if (cl_particles_quake.integer)
613 if (cl_particles_smoke.integer)
614 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
617 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
620 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
621 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
622 CL_AllocDlight(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);
624 else if (effectnameindex == EFFECT_TE_BLOOD)
626 if (!cl_particles_blood.integer)
628 if (cl_particles_quake.integer)
629 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
632 static double bloodaccumulator = 0;
633 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
634 for (;bloodaccumulator > 0;bloodaccumulator--)
635 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 0, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 0, 64);
638 else if (effectnameindex == EFFECT_TE_SPARK)
639 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count, 0);
640 else if (effectnameindex == EFFECT_TE_PLASMABURN)
642 // plasma scorch mark
643 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
644 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
645 CL_AllocDlight(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
647 else if (effectnameindex == EFFECT_TE_GUNSHOT)
649 if (cl_particles_bulletimpacts.integer)
651 if (cl_particles_quake.integer)
652 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
654 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
657 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
658 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
660 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
662 if (cl_particles_bulletimpacts.integer)
664 if (cl_particles_quake.integer)
665 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
667 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
670 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
671 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
672 CL_AllocDlight(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);
674 else if (effectnameindex == EFFECT_TE_EXPLOSION)
676 CL_ParticleExplosion(center);
677 CL_AllocDlight(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);
679 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
681 CL_ParticleExplosion(center);
682 CL_AllocDlight(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);
684 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
686 if (cl_particles_quake.integer)
689 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
692 particle(particletype + pt_static, particlepalette[66], particlepalette[71], tex_particle, 1, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, 16, 256);
694 particle(particletype + pt_static, particlepalette[150], particlepalette[155], tex_particle, 1, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 16, 0);
698 CL_ParticleExplosion(center);
699 CL_AllocDlight(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);
701 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
702 CL_AllocDlight(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
703 else if (effectnameindex == EFFECT_TE_FLAMEJET)
705 count *= cl_particles_quality.value;
707 particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 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, 0, 128);
709 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
711 float i, j, inc, vel;
714 inc = 8 / cl_particles_quality.value;
715 for (i = -128;i < 128;i += inc)
717 for (j = -128;j < 128;j += inc)
719 dir[0] = j + lhrandom(0, inc);
720 dir[1] = i + lhrandom(0, inc);
722 org[0] = center[0] + dir[0];
723 org[1] = center[1] + dir[1];
724 org[2] = center[2] + lhrandom(0, 64);
725 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
726 particle(particletype + pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1, inc * lhrandom(24, 32), inc * 12, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0);
730 else if (effectnameindex == EFFECT_TE_TELEPORT)
732 float i, j, k, inc, vel;
735 inc = 4 / cl_particles_quality.value;
736 for (i = -16;i < 16;i += inc)
738 for (j = -16;j < 16;j += inc)
740 for (k = -24;k < 32;k += inc)
742 VectorSet(dir, i*8, j*8, k*8);
743 VectorNormalize(dir);
744 vel = lhrandom(50, 113);
745 particle(particletype + pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1, 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);
749 CL_AllocDlight(NULL, &tempmatrix, 200, 1.0f, 1.0f, 1.0f, 600, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
751 else if (effectnameindex == EFFECT_TE_TEI_G3)
752 particle(particletype + pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0);
753 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
755 if (cl_particles_smoke.integer)
757 count *= 0.25f * cl_particles_quality.value;
759 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 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, 1.5f, 6.0f);
762 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
764 CL_ParticleExplosion(center);
765 CL_AllocDlight(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);
767 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
770 if (cl_stainmaps.integer)
771 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
772 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
773 if (cl_particles_smoke.integer)
774 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
775 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 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, 20, 155);
776 if (cl_particles_sparks.integer)
777 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
778 particle(particletype + pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 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, 465);
779 CL_AllocDlight(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);
781 else if (effectnameindex == EFFECT_EF_FLAME)
783 count *= 300 * cl_particles_quality.value;
785 particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 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, 16, 128);
786 CL_AllocDlight(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);
788 else if (effectnameindex == EFFECT_EF_STARDUST)
790 count *= 200 * cl_particles_quality.value;
792 particle(particletype + pt_static, 0x903010, 0xFFD030, tex_particle, 4, 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, 16, 128);
793 CL_AllocDlight(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);
795 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
799 int smoke, blood, bubbles, r, color;
801 if (effectnameindex == EFFECT_TR_ROCKET)
802 CL_AllocDlight(&ent->render, &ent->render.matrix, 200, 3.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
803 else if (effectnameindex == EFFECT_TR_VORESPIKE)
805 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
806 CL_AllocDlight(&ent->render, &ent->render.matrix, 100, 0.3f, 0.6f, 1.2f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
808 CL_AllocDlight(&ent->render, &ent->render.matrix, 200, 1.2f, 0.5f, 1.0f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
810 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
811 CL_AllocDlight(&ent->render, &ent->render.matrix, 200, 0.75f, 1.5f, 3.0f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
813 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
816 VectorSubtract(originmaxs, originmins, dir);
817 len = VectorNormalizeLength(dir);
818 dec = -ent->persistent.trail_time;
819 ent->persistent.trail_time += len;
820 if (ent->persistent.trail_time < 0.01f)
823 // if we skip out, leave it reset
824 ent->persistent.trail_time = 0.0f;
826 // advance into this frame to reach the first puff location
827 VectorMA(originmins, dec, dir, pos);
830 smoke = cl_particles.integer && cl_particles_smoke.integer;
831 blood = cl_particles.integer && cl_particles_blood.integer;
832 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
833 qd = 1.0f / cl_particles_quality.value;
840 if (effectnameindex == EFFECT_TR_BLOOD)
842 if (cl_particles_quake.integer)
844 color = particlepalette[67 + (rand()&3)];
845 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
850 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 0, 64);
853 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
855 if (cl_particles_quake.integer)
858 color = particlepalette[67 + (rand()&3)];
859 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
864 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 0, 64);
870 if (effectnameindex == EFFECT_TR_ROCKET)
872 if (cl_particles_quake.integer)
875 color = particlepalette[ramp3[r]];
876 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
880 particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 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);
881 particle(particletype + pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 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, 20);
884 else if (effectnameindex == EFFECT_TR_GRENADE)
886 if (cl_particles_quake.integer)
889 color = particlepalette[ramp3[r]];
890 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
894 particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*50, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
897 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
899 if (cl_particles_quake.integer)
902 color = particlepalette[52 + (rand()&7)];
903 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0);
904 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0);
906 else if (gamemode == GAME_GOODVSBAD2)
909 particle(particletype + pt_static, 0x00002E, 0x000030, tex_particle, 6, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
913 color = particlepalette[20 + (rand()&7)];
914 particle(particletype + pt_static, color, color, tex_particle, 2, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
917 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
919 if (cl_particles_quake.integer)
922 color = particlepalette[230 + (rand()&7)];
923 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0);
924 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0);
928 color = particlepalette[226 + (rand()&7)];
929 particle(particletype + pt_static, color, color, tex_particle, 2, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
932 else if (effectnameindex == EFFECT_TR_VORESPIKE)
934 if (cl_particles_quake.integer)
936 color = particlepalette[152 + (rand()&3)];
937 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 8, 0);
939 else if (gamemode == GAME_GOODVSBAD2)
942 particle(particletype + pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
944 else if (gamemode == GAME_PRYDON)
947 particle(particletype + pt_static, 0x103040, 0x204050, tex_particle, 6, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
950 particle(particletype + pt_static, 0x502030, 0x502030, tex_particle, 3, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
952 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
955 particle(particletype + pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 4);
957 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
960 particle(particletype + pt_static, 0x283880, 0x283880, tex_particle, 4, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 16);
962 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
963 particle(particletype + pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
967 if (effectnameindex == EFFECT_TR_ROCKET)
968 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, (1.0 / 16.0), 0, 16);
969 else if (effectnameindex == EFFECT_TR_GRENADE)
970 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, (1.0 / 16.0), 0, 16);
972 // advance to next time and position
975 VectorMA (pos, dec, dir, pos);
977 ent->persistent.trail_time = len;
979 else if (developer.integer >= 1)
980 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
983 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)
986 qboolean found = false;
987 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
988 return; // invalid effect index
989 if (!particleeffectname[effectnameindex][0])
990 return; // no such effect
991 VectorLerp(originmins, 0.5, originmaxs, center);
992 if (effectnameindex == EFFECT_SVC_PARTICLE)
994 if (!cl_particles.integer)
996 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
998 CL_ParticleExplosion(center);
999 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
1000 CL_ParticleEffect(EFFECT_TE_BLOOD, pcount / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1003 float count = pcount * cl_particles_quality.value;
1004 for (;count > 0;count--)
1006 int k = particlepalette[palettecolor + (rand()&7)];
1007 if (cl_particles_quake.integer)
1008 particle(particletype + pt_alphastatic, k, k, tex_particle, 1, lhrandom(51, 255), 512, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 8, 0);
1009 else if (gamemode == GAME_GOODVSBAD2)
1010 particle(particletype + pt_alphastatic, k, k, tex_particle, 5, 255, 300, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 8, 10);
1012 particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 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, 8, 15);
1016 else if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1018 int effectinfoindex;
1021 particleeffectinfo_t *info;
1023 vec3_t centervelocity;
1027 qboolean underwater;
1028 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1029 VectorLerp(originmins, 0.5, originmaxs, center);
1030 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1031 supercontents = CL_PointSuperContents(center);
1032 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1033 VectorSubtract(originmaxs, originmins, traildir);
1034 VectorNormalize(traildir);
1035 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1037 if (info->effectnameindex == effectnameindex)
1040 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1042 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1045 // spawn a dlight if requested
1046 if (info->lightradiusstart > 0)
1048 matrix4x4_t tempmatrix;
1049 if (info->trailspacing > 0)
1050 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1052 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1053 CL_AllocDlight(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);
1058 if (info->tex[1] > info->tex[0])
1060 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1061 tex = min(tex, info->tex[1] - 1);
1063 if (info->particletype == pt_decal)
1064 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]);
1065 else if (info->particletype == pt_beam)
1066 particle(particletype + info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), 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);
1069 if (!cl_particles.integer)
1071 switch (info->particletype)
1073 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1074 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1075 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1076 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1079 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1080 VectorCopy(originmins, trailpos);
1081 for (;info->particleaccumulator > 0;info->particleaccumulator--)
1083 if (info->tex[1] > info->tex[0])
1085 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1086 tex = min(tex, info->tex[1] - 1);
1088 if (info->trailspacing <= 0)
1090 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1091 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1092 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1095 particle(particletype + info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), 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, 0, 0);
1096 if (info->trailspacing > 0)
1097 VectorMA(trailpos, info->trailspacing, traildir, trailpos);
1104 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor);
1112 void CL_EntityParticles (const entity_t *ent)
1115 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1116 static vec3_t avelocities[NUMVERTEXNORMALS];
1117 if (!cl_particles.integer) return;
1119 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1121 if (!avelocities[0][0])
1122 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1123 avelocities[0][i] = lhrandom(0, 2.55);
1125 for (i = 0;i < NUMVERTEXNORMALS;i++)
1127 yaw = cl.time * avelocities[i][0];
1128 pitch = cl.time * avelocities[i][1];
1129 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1130 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1131 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1132 particle(particletype + pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0);
1137 void CL_ReadPointFile_f (void)
1139 vec3_t org, leakorg;
1141 char *pointfile = NULL, *pointfilepos, *t, tchar;
1142 char name[MAX_OSPATH];
1147 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1148 strlcat (name, ".pts", sizeof (name));
1149 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1152 Con_Printf("Could not open %s\n", name);
1156 Con_Printf("Reading %s...\n", name);
1157 VectorClear(leakorg);
1160 pointfilepos = pointfile;
1161 while (*pointfilepos)
1163 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1168 while (*t && *t != '\n' && *t != '\r')
1172 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1178 VectorCopy(org, leakorg);
1181 if (cl.num_particles < cl.max_particles - 3)
1184 particle(particletype + pt_static, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0);
1187 Mem_Free(pointfile);
1188 VectorCopy(leakorg, org);
1189 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1191 particle(particletype + pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0);
1192 particle(particletype + pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0);
1193 particle(particletype + pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0);
1198 CL_ParseParticleEffect
1200 Parse an effect out of the server message
1203 void CL_ParseParticleEffect (void)
1206 int i, count, msgcount, color;
1208 MSG_ReadVector(org, cls.protocol);
1209 for (i=0 ; i<3 ; i++)
1210 dir[i] = MSG_ReadChar ();
1211 msgcount = MSG_ReadByte ();
1212 color = MSG_ReadByte ();
1214 if (msgcount == 255)
1219 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1224 CL_ParticleExplosion
1228 void CL_ParticleExplosion (const vec3_t org)
1234 if (cl_stainmaps.integer)
1235 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1236 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1238 if (cl_particles_quake.integer)
1240 for (i = 0;i < 1024;i++)
1246 color = particlepalette[ramp1[r]];
1247 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, 16, 256);
1251 color = particlepalette[ramp2[r]];
1252 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 16, 256);
1258 i = CL_PointSuperContents(org);
1259 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1261 if (cl_particles.integer && cl_particles_bubbles.integer)
1262 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1263 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, (1.0 / 16.0), 16, 96);
1267 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1269 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1271 for (i = 0;i < 32;i++)
1275 for (k = 0;k < 16;k++)
1277 v[0] = org[0] + lhrandom(-48, 48);
1278 v[1] = org[1] + lhrandom(-48, 48);
1279 v[2] = org[2] + lhrandom(-48, 48);
1280 trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
1281 if (trace.fraction >= 0.1)
1284 VectorSubtract(trace.endpos, org, v2);
1285 VectorScale(v2, 2.0f, v2);
1286 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 12, 32, 64, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0);
1290 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1291 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1292 particle(particletype + pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, lhrandom(0, 255), 512, 1, 0, org[0], org[1], org[2], 0, 0, 80, 0.2, 0, 256);
1296 if (cl_particles_explosions_shell.integer)
1297 R_NewExplosion(org);
1302 CL_ParticleExplosion2
1306 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1309 if (!cl_particles.integer) return;
1311 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1313 k = particlepalette[colorStart + (i % colorLength)];
1314 if (cl_particles_quake.integer)
1315 particle(particletype + pt_static, k, k, tex_particle, 1, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, 8, 256);
1317 particle(particletype + pt_static, k, k, tex_particle, lhrandom(0.5, 1.5), 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), 8, 192);
1321 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1323 if (cl_particles_sparks.integer)
1325 sparkcount *= cl_particles_quality.value;
1326 while(sparkcount-- > 0)
1327 particle(particletype + pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.4f, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + sv_gravity.value * 0.1, 0, 0, 64);
1329 if (cl_particles_smoke.integer)
1331 smokecount *= cl_particles_quality.value;
1332 while(smokecount-- > 0)
1333 particle(particletype + pt_smoke, 0x101010, 0x202020, tex_smoke[rand()&7], 3, 255, 1024, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8);
1337 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)
1340 if (!cl_particles.integer) return;
1342 count = (int)(count * cl_particles_quality.value);
1345 k = particlepalette[colorbase + (rand()&3)];
1346 particle(particletype + pt_alphastatic, k, k, tex_particle, 2, 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, randomvel);
1350 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1353 float z, minz, maxz;
1355 if (!cl_particles.integer) return;
1356 if (dir[2] < 0) // falling
1361 minz = z - fabs(dir[2]) * 0.1;
1362 maxz = z + fabs(dir[2]) * 0.1;
1363 minz = bound(mins[2], minz, maxs[2]);
1364 maxz = bound(mins[2], maxz, maxs[2]);
1366 count = (int)(count * cl_particles_quality.value);
1371 count *= 4; // ick, this should be in the mod or maps?
1375 k = particlepalette[colorbase + (rand()&3)];
1376 if (gamemode == GAME_GOODVSBAD2)
1377 particle(particletype + pt_rain, k, k, tex_particle, 20, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0);
1379 particle(particletype + pt_rain, k, k, tex_particle, 0.5, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0);
1385 k = particlepalette[colorbase + (rand()&3)];
1386 if (gamemode == GAME_GOODVSBAD2)
1387 p = particle(particletype + pt_snow, k, k, tex_particle, 20, 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);
1389 p = particle(particletype + pt_snow, k, k, tex_particle, 1, 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);
1391 VectorCopy(p->vel, p->relativedirection);
1395 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1404 void CL_MoveParticles (void)
1407 int i, maxparticle, j, a, content;
1408 float gravity, dvel, bloodwaterfade, frametime, f, dist, org[3], oldorg[3];
1412 // LordHavoc: early out condition
1413 if (!cl.num_particles)
1415 cl.free_particle = 0;
1419 frametime = cl.time - cl.oldtime;
1420 gravity = frametime * sv_gravity.value;
1421 dvel = 1+4*frametime;
1422 bloodwaterfade = max(cl_particles_blood_alpha.value, 0.01f) * frametime * 128.0f;
1426 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1433 p->alpha -= p->alphafade * frametime;
1441 if (p->type->orientation != PARTICLE_BEAM)
1443 VectorCopy(p->org, oldorg);
1444 VectorMA(p->org, frametime, p->vel, p->org);
1445 VectorCopy(p->org, org);
1448 trace = CL_TraceBox(oldorg, vec3_origin, vec3_origin, p->org, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | (p->type == particletype + pt_rain ? SUPERCONTENTS_LIQUIDSMASK : 0), false);
1449 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1450 // or if the trace hit something flagged as NOIMPACT
1451 // then remove the particle
1452 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1457 // react if the particle hit something
1458 if (trace.fraction < 1)
1460 VectorCopy(trace.endpos, p->org);
1461 if (p->type == particletype + pt_rain)
1463 // raindrop - splash on solid/water/slime/lava
1465 // convert from a raindrop particle to a rainsplash decal
1466 VectorCopy(trace.plane.normal, p->vel);
1467 VectorAdd(p->org, p->vel, p->org);
1468 p->type = particletype + pt_raindecal;
1469 p->texnum = tex_rainsplash[0];
1471 p->alphafade = p->alpha / 0.4;
1478 particle(particletype + pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, lhrandom(64, 255), 512, 1, 0, p->org[0], p->org[1], p->org[2], p->vel[0]*16, p->vel[1]*16, 32 + p->vel[2]*16, 0, 0, 32);
1480 else if (p->type == particletype + pt_blood)
1482 // blood - splash on solid
1483 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1488 if (!cl_decals.integer)
1493 // convert from a blood particle to a blood decal
1494 VectorCopy(trace.plane.normal, p->vel);
1495 VectorAdd(p->org, p->vel, p->org);
1496 if (cl_stainmaps.integer)
1497 R_Stain(p->org, 32, 32, 16, 16, (int)(p->alpha * p->size * (1.0f / 40.0f)), 192, 48, 48, (int)(p->alpha * p->size * (1.0f / 40.0f)));
1499 p->type = particletype + pt_decal;
1500 p->texnum = tex_blooddecal[rand()&7];
1502 p->ownermodel = cl.entities[hitent].render.model;
1503 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1504 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1512 else if (p->bounce < 0)
1514 // bounce -1 means remove on impact
1520 // anything else - bounce off solid
1521 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1522 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1523 if (DotProduct(p->vel, p->vel) < 0.03)
1524 VectorClear(p->vel);
1528 p->vel[2] -= p->gravity * gravity;
1532 f = p->friction * frametime;
1533 if (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1536 VectorScale(p->vel, f, p->vel);
1540 if (p->type != particletype + pt_static)
1542 switch (p->type - particletype)
1544 case pt_entityparticle:
1545 // particle that removes itself after one rendered frame
1552 a = CL_PointSuperContents(p->org);
1553 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1555 p->size += frametime * 8;
1556 //p->alpha -= bloodwaterfade;
1559 p->vel[2] -= gravity;
1560 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1564 a = CL_PointSuperContents(p->org);
1565 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1572 a = CL_PointSuperContents(p->org);
1573 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1577 if (cl.time > p->time2)
1580 p->time2 = cl.time + (rand() & 3) * 0.1;
1581 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1582 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1583 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1585 a = CL_PointSuperContents(p->org);
1586 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1590 //p->size += frametime * 15;
1593 // FIXME: this has fairly wacky handling of alpha
1594 p->alphafade = cl.time > (p->time2 + cl_decals_time.value) ? (255 / cl_decals_fadetime.value) : 0;
1595 if (cl.entities[p->owner].render.model == p->ownermodel)
1597 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1598 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1604 a = (int)max(0, (cl.time - p->time2) * 40);
1606 p->texnum = tex_rainsplash[a];
1615 cl.num_particles = maxparticle + 1;
1616 cl.free_particle = 0;
1619 #define MAX_PARTICLETEXTURES 64
1620 // particletexture_t is a rectangle in the particlefonttexture
1621 typedef struct particletexture_s
1623 rtexture_t *texture;
1624 float s1, t1, s2, t2;
1628 static rtexturepool_t *particletexturepool;
1629 static rtexture_t *particlefonttexture;
1630 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1632 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1634 #define PARTICLETEXTURESIZE 64
1635 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1637 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1641 dz = 1 - (dx*dx+dy*dy);
1642 if (dz > 0) // it does hit the sphere
1646 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1647 VectorNormalize(normal);
1648 dot = DotProduct(normal, light);
1649 if (dot > 0.5) // interior reflection
1650 f += ((dot * 2) - 1);
1651 else if (dot < -0.5) // exterior reflection
1652 f += ((dot * -2) - 1);
1654 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1655 VectorNormalize(normal);
1656 dot = DotProduct(normal, light);
1657 if (dot > 0.5) // interior reflection
1658 f += ((dot * 2) - 1);
1659 else if (dot < -0.5) // exterior reflection
1660 f += ((dot * -2) - 1);
1662 f += 16; // just to give it a haze so you can see the outline
1663 f = bound(0, f, 255);
1664 return (unsigned char) f;
1670 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1672 int basex, basey, y;
1673 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1674 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1675 particletexture[texnum].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1676 particletexture[texnum].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1677 particletexture[texnum].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1678 particletexture[texnum].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1679 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1680 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1683 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1686 float cx, cy, dx, dy, f, iradius;
1688 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1689 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1690 iradius = 1.0f / radius;
1691 alpha *= (1.0f / 255.0f);
1692 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1694 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1698 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1701 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1702 d[0] += (int)(f * (red - d[0]));
1703 d[1] += (int)(f * (green - d[1]));
1704 d[2] += (int)(f * (blue - d[2]));
1710 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1713 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1715 data[0] = bound(minr, data[0], maxr);
1716 data[1] = bound(ming, data[1], maxg);
1717 data[2] = bound(minb, data[2], maxb);
1721 void particletextureinvert(unsigned char *data)
1724 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1726 data[0] = 255 - data[0];
1727 data[1] = 255 - data[1];
1728 data[2] = 255 - data[2];
1732 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1733 static void R_InitBloodTextures (unsigned char *particletexturedata)
1736 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1739 for (i = 0;i < 8;i++)
1741 memset(&data[0][0][0], 255, sizeof(data));
1742 for (k = 0;k < 24;k++)
1743 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1744 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1745 particletextureinvert(&data[0][0][0]);
1746 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1750 for (i = 0;i < 8;i++)
1752 memset(&data[0][0][0], 255, sizeof(data));
1754 for (j = 1;j < 10;j++)
1755 for (k = min(j, m - 1);k < m;k++)
1756 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1757 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1758 particletextureinvert(&data[0][0][0]);
1759 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1764 static void R_InitParticleTexture (void)
1766 int x, y, d, i, k, m;
1767 float dx, dy, radius, f, f2;
1768 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4], noise3[64][64], data2[64][16][4];
1770 unsigned char *particletexturedata;
1772 // a note: decals need to modulate (multiply) the background color to
1773 // properly darken it (stain), and they need to be able to alpha fade,
1774 // this is a very difficult challenge because it means fading to white
1775 // (no change to background) rather than black (darkening everything
1776 // behind the whole decal polygon), and to accomplish this the texture is
1777 // inverted (dark red blood on white background becomes brilliant cyan
1778 // and white on black background) so we can alpha fade it to black, then
1779 // we invert it again during the blendfunc to make it work...
1781 particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1782 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1785 for (i = 0;i < 8;i++)
1787 memset(&data[0][0][0], 255, sizeof(data));
1790 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1792 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1793 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1795 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1797 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1798 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1800 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1801 d = (noise2[y][x] - 128) * 3 + 192;
1803 d = (int)(d * (1-(dx*dx+dy*dy)));
1804 d = (d * noise1[y][x]) >> 7;
1805 d = bound(0, d, 255);
1806 data[y][x][3] = (unsigned char) d;
1813 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1817 for (i = 0;i < 16;i++)
1819 memset(&data[0][0][0], 255, sizeof(data));
1820 radius = i * 3.0f / 4.0f / 16.0f;
1821 f2 = 255.0f * ((15.0f - i) / 15.0f);
1822 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1824 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1825 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1827 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1828 f = f2 * (1.0 - 4.0f * fabs(radius - sqrt(dx*dx+dy*dy)));
1829 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1832 setuptex(tex_rainsplash[i], &data[0][0][0], particletexturedata);
1836 memset(&data[0][0][0], 255, sizeof(data));
1837 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1839 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1840 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1842 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1843 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1844 d = bound(0, d, 255);
1845 data[y][x][3] = (unsigned char) d;
1848 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1851 memset(&data[0][0][0], 255, sizeof(data));
1852 light[0] = 1;light[1] = 1;light[2] = 1;
1853 VectorNormalize(light);
1854 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1856 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1857 // stretch upper half of bubble by +50% and shrink lower half by -50%
1858 // (this gives an elongated teardrop shape)
1860 dy = (dy - 0.5f) * 2.0f;
1862 dy = (dy - 0.5f) / 1.5f;
1863 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1865 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1866 // shrink bubble width to half
1868 data[y][x][3] = shadebubble(dx, dy, light);
1871 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1874 memset(&data[0][0][0], 255, sizeof(data));
1875 light[0] = 1;light[1] = 1;light[2] = 1;
1876 VectorNormalize(light);
1877 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1879 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1880 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1882 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1883 data[y][x][3] = shadebubble(dx, dy, light);
1886 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1888 // Blood particles and blood decals
1889 R_InitBloodTextures (particletexturedata);
1892 for (i = 0;i < 8;i++)
1894 memset(&data[0][0][0], 255, sizeof(data));
1895 for (k = 0;k < 12;k++)
1896 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1897 for (k = 0;k < 3;k++)
1898 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1899 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1900 particletextureinvert(&data[0][0][0]);
1901 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1905 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1908 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1909 if (!particlefonttexture)
1910 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1911 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1912 particletexture[i].texture = particlefonttexture;
1915 fractalnoise(&noise3[0][0], 64, 4);
1917 for (y = 0;y < 64;y++)
1919 dy = (y - 0.5f*64) / (64*0.5f-1);
1920 for (x = 0;x < 16;x++)
1922 dx = (x - 0.5f*16) / (16*0.5f-2);
1923 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1924 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1925 data2[y][x][3] = 255;
1930 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1933 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1934 if (!particletexture[tex_beam].texture)
1935 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1936 particletexture[tex_beam].s1 = 0;
1937 particletexture[tex_beam].t1 = 0;
1938 particletexture[tex_beam].s2 = 1;
1939 particletexture[tex_beam].t2 = 1;
1940 Mem_Free(particletexturedata);
1943 static void r_part_start(void)
1945 particletexturepool = R_AllocTexturePool();
1946 R_InitParticleTexture ();
1947 CL_Particles_LoadEffectInfo();
1950 static void r_part_shutdown(void)
1952 R_FreeTexturePool(&particletexturepool);
1955 static void r_part_newmap(void)
1959 void R_Particles_Init (void)
1961 Cvar_RegisterVariable(&r_drawparticles);
1962 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1965 float particle_vertex3f[12], particle_texcoord2f[8];
1967 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight)
1969 const particle_t *p = cl.particles + surfacenumber;
1972 float org[3], up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
1973 particletexture_t *tex;
1975 VectorCopy(p->org, org);
1977 blendmode = p->type->blendmode;
1978 tex = &particletexture[p->texnum];
1979 cr = p->color[0] * (1.0f / 255.0f);
1980 cg = p->color[1] * (1.0f / 255.0f);
1981 cb = p->color[2] * (1.0f / 255.0f);
1982 ca = p->alpha * (1.0f / 255.0f);
1983 if (blendmode == PBLEND_MOD)
1993 ca /= cl_particles_quality.value;
1994 if (p->type->lighting)
1996 float ambient[3], diffuse[3], diffusenormal[3];
1997 R_CompleteLightPoint(ambient, diffuse, diffusenormal, org, true);
1998 cr *= (ambient[0] + 0.5 * diffuse[0]);
1999 cg *= (ambient[1] + 0.5 * diffuse[1]);
2000 cb *= (ambient[2] + 0.5 * diffuse[2]);
2004 fog = VERTEXFOGTABLE(VectorDistance(org, r_vieworigin));
2009 if (blendmode == PBLEND_ALPHA)
2011 cr += fogcolor[0] * fog;
2012 cg += fogcolor[1] * fog;
2013 cb += fogcolor[2] * fog;
2017 R_Mesh_Matrix(&identitymatrix);
2019 memset(&m, 0, sizeof(m));
2020 m.tex[0] = R_GetTexture(tex->texture);
2021 m.pointer_texcoord[0] = particle_texcoord2f;
2022 m.pointer_vertex = particle_vertex3f;
2025 GL_Color(cr, cg, cb, ca);
2027 if (blendmode == PBLEND_ALPHA)
2028 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2029 else if (blendmode == PBLEND_ADD)
2030 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2031 else //if (blendmode == PBLEND_MOD)
2032 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2033 GL_DepthMask(false);
2035 size = p->size * cl_particles_size.value;
2036 if (p->type->orientation == PARTICLE_BILLBOARD || p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2038 if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2041 if (DotProduct(p->vel, r_vieworigin) > DotProduct(p->vel, org))
2043 VectorNegate(p->vel, v);
2044 VectorVectors(v, right, up);
2047 VectorVectors(p->vel, right, up);
2048 VectorScale(right, size, right);
2049 VectorScale(up, size, up);
2053 VectorScale(r_viewleft, -size, right);
2054 VectorScale(r_viewup, size, up);
2056 particle_vertex3f[ 0] = org[0] - right[0] - up[0];
2057 particle_vertex3f[ 1] = org[1] - right[1] - up[1];
2058 particle_vertex3f[ 2] = org[2] - right[2] - up[2];
2059 particle_vertex3f[ 3] = org[0] - right[0] + up[0];
2060 particle_vertex3f[ 4] = org[1] - right[1] + up[1];
2061 particle_vertex3f[ 5] = org[2] - right[2] + up[2];
2062 particle_vertex3f[ 6] = org[0] + right[0] + up[0];
2063 particle_vertex3f[ 7] = org[1] + right[1] + up[1];
2064 particle_vertex3f[ 8] = org[2] + right[2] + up[2];
2065 particle_vertex3f[ 9] = org[0] + right[0] - up[0];
2066 particle_vertex3f[10] = org[1] + right[1] - up[1];
2067 particle_vertex3f[11] = org[2] + right[2] - up[2];
2068 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2069 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2070 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2071 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2073 else if (p->type->orientation == PARTICLE_SPARK)
2075 VectorMA(p->org, -0.02, p->vel, v);
2076 VectorMA(p->org, 0.02, p->vel, up2);
2077 R_CalcBeam_Vertex3f(particle_vertex3f, v, up2, size);
2078 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2079 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2080 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2081 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2083 else if (p->type->orientation == PARTICLE_BEAM)
2085 R_CalcBeam_Vertex3f(particle_vertex3f, p->org, p->vel, size);
2086 VectorSubtract(p->vel, p->org, up);
2087 VectorNormalize(up);
2088 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2089 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2090 particle_texcoord2f[0] = 1;particle_texcoord2f[1] = v[0];
2091 particle_texcoord2f[2] = 0;particle_texcoord2f[3] = v[0];
2092 particle_texcoord2f[4] = 0;particle_texcoord2f[5] = v[1];
2093 particle_texcoord2f[6] = 1;particle_texcoord2f[7] = v[1];
2097 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2101 R_Mesh_Draw(0, 4, 2, polygonelements);
2104 void R_DrawParticles (void)
2107 float minparticledist;
2110 // LordHavoc: early out conditions
2111 if ((!cl.num_particles) || (!r_drawparticles.integer))
2114 minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
2116 // LordHavoc: only render if not too close
2117 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2121 renderstats.particles++;
2122 if (DotProduct(p->org, r_viewforward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2124 if (p->type == particletype + pt_decal)
2125 R_DrawParticle_TransparentCallback(0, i, 0);
2127 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);