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] = strtol(argv[1+arrayindex], NULL, 0)
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 = strtol(argv[1], NULL, 0)
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]);
296 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
297 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
298 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
299 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
300 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
301 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
302 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
303 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
304 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
305 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
306 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
307 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
308 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
309 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
310 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
311 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
312 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
313 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
314 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
315 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
316 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
317 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
318 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
320 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
329 int CL_ParticleEffectIndexForName(const char *name)
332 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
333 if (!strcmp(particleeffectname[i], name))
338 const char *CL_ParticleEffectNameForIndex(int i)
340 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
342 return particleeffectname[i];
345 // MUST match effectnameindex_t in client.h
346 static const char *standardeffectnames[EFFECT_TOTAL] =
371 "TE_TEI_BIGEXPLOSION",
387 void CL_Particles_LoadEffectInfo(void)
390 unsigned char *filedata;
391 fs_offset_t filesize;
392 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
393 memset(particleeffectname, 0, sizeof(particleeffectname));
394 for (i = 0;i < EFFECT_TOTAL;i++)
395 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
396 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
399 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
409 void CL_ReadPointFile_f (void);
410 void CL_Particles_Init (void)
412 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)");
413 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
415 Cvar_RegisterVariable (&cl_particles);
416 Cvar_RegisterVariable (&cl_particles_quality);
417 Cvar_RegisterVariable (&cl_particles_size);
418 Cvar_RegisterVariable (&cl_particles_quake);
419 Cvar_RegisterVariable (&cl_particles_blood);
420 Cvar_RegisterVariable (&cl_particles_blood_alpha);
421 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
422 Cvar_RegisterVariable (&cl_particles_explosions_smoke);
423 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
424 Cvar_RegisterVariable (&cl_particles_explosions_shell);
425 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
426 Cvar_RegisterVariable (&cl_particles_smoke);
427 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
428 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
429 Cvar_RegisterVariable (&cl_particles_sparks);
430 Cvar_RegisterVariable (&cl_particles_bubbles);
431 Cvar_RegisterVariable (&cl_decals);
432 Cvar_RegisterVariable (&cl_decals_time);
433 Cvar_RegisterVariable (&cl_decals_fadetime);
436 void CL_Particles_Shutdown (void)
440 // list of all 26 parameters:
441 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
442 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
443 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
444 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
445 // palpha - opacity of particle as 0-255 (can be more than 255)
446 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
447 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
448 // pgravity - how much effect gravity has on the particle (0-1)
449 // 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
450 // px,py,pz - starting origin of particle
451 // pvx,pvy,pvz - starting velocity of particle
452 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
453 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)
458 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].type;cl.free_particle++);
459 if (cl.free_particle >= cl.max_particles)
461 part = &cl.particles[cl.free_particle++];
462 if (cl.num_particles < cl.free_particle)
463 cl.num_particles = cl.free_particle;
464 memset(part, 0, sizeof(*part));
466 l2 = (int)lhrandom(0.5, 256.5);
468 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
469 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
470 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
471 part->color[3] = 0xFF;
474 part->alpha = palpha;
475 part->alphafade = palphafade;
476 part->gravity = pgravity;
477 part->bounce = pbounce;
479 part->org[0] = px + originjitter * v[0];
480 part->org[1] = py + originjitter * v[1];
481 part->org[2] = pz + originjitter * v[2];
482 part->vel[0] = pvx + velocityjitter * v[0];
483 part->vel[1] = pvy + velocityjitter * v[1];
484 part->vel[2] = pvz + velocityjitter * v[2];
486 part->friction = pfriction;
490 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
493 if (!cl_decals.integer)
495 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);
500 p->ownermodel = cl.entities[p->owner].render.model;
501 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
502 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
503 VectorAdd(p->relativeorigin, p->relativedirection, p->relativeorigin);
507 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
510 float bestfrac, bestorg[3], bestnormal[3];
512 int besthitent = 0, hitent;
515 for (i = 0;i < 32;i++)
518 VectorMA(org, maxdist, org2, org2);
519 trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, false);
520 // take the closest trace result that doesn't end up hitting a NOMARKS
521 // surface (sky for example)
522 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
524 bestfrac = trace.fraction;
526 VectorCopy(trace.endpos, bestorg);
527 VectorCopy(trace.plane.normal, bestnormal);
531 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
534 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount);
535 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)
538 matrix4x4_t tempmatrix;
539 VectorLerp(originmins, 0.5, originmaxs, center);
540 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
541 if (effectnameindex == EFFECT_SVC_PARTICLE)
543 if (cl_particles.integer)
545 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
547 CL_ParticleExplosion(center);
548 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
549 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
552 count *= cl_particles_quality.value;
553 for (;count > 0;count--)
555 int k = particlepalette[palettecolor + (rand()&7)];
556 if (cl_particles_quake.integer)
557 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);
558 else if (gamemode == GAME_GOODVSBAD2)
559 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);
561 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);
566 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
567 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
568 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
569 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
570 else if (effectnameindex == EFFECT_TE_SPIKE)
572 if (cl_particles_bulletimpacts.integer)
574 if (cl_particles_quake.integer)
576 if (cl_particles_smoke.integer)
577 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
580 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
583 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
584 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
586 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
588 if (cl_particles_bulletimpacts.integer)
590 if (cl_particles_quake.integer)
592 if (cl_particles_smoke.integer)
593 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
596 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
599 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
600 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
601 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);
603 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
605 if (cl_particles_bulletimpacts.integer)
607 if (cl_particles_quake.integer)
609 if (cl_particles_smoke.integer)
610 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
613 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
616 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
617 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
619 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
621 if (cl_particles_bulletimpacts.integer)
623 if (cl_particles_quake.integer)
625 if (cl_particles_smoke.integer)
626 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
629 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
632 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
633 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
634 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);
636 else if (effectnameindex == EFFECT_TE_BLOOD)
638 if (!cl_particles_blood.integer)
640 if (cl_particles_quake.integer)
641 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
644 static double bloodaccumulator = 0;
645 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
646 for (;bloodaccumulator > 0;bloodaccumulator--)
647 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);
650 else if (effectnameindex == EFFECT_TE_SPARK)
651 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count, 0);
652 else if (effectnameindex == EFFECT_TE_PLASMABURN)
654 // plasma scorch mark
655 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
656 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
657 CL_AllocDlight(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
659 else if (effectnameindex == EFFECT_TE_GUNSHOT)
661 if (cl_particles_bulletimpacts.integer)
663 if (cl_particles_quake.integer)
664 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
666 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
669 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
670 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
672 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
674 if (cl_particles_bulletimpacts.integer)
676 if (cl_particles_quake.integer)
677 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
679 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
682 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
683 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
684 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);
686 else if (effectnameindex == EFFECT_TE_EXPLOSION)
688 CL_ParticleExplosion(center);
689 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);
691 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
693 CL_ParticleExplosion(center);
694 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);
696 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
698 if (cl_particles_quake.integer)
701 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
704 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);
706 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);
710 CL_ParticleExplosion(center);
711 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);
713 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
714 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);
715 else if (effectnameindex == EFFECT_TE_FLAMEJET)
717 count *= cl_particles_quality.value;
719 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);
721 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
723 float i, j, inc, vel;
726 inc = 8 / cl_particles_quality.value;
727 for (i = -128;i < 128;i += inc)
729 for (j = -128;j < 128;j += inc)
731 dir[0] = j + lhrandom(0, inc);
732 dir[1] = i + lhrandom(0, inc);
734 org[0] = center[0] + dir[0];
735 org[1] = center[1] + dir[1];
736 org[2] = center[2] + lhrandom(0, 64);
737 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
738 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);
742 else if (effectnameindex == EFFECT_TE_TELEPORT)
744 float i, j, k, inc, vel;
747 inc = 4 / cl_particles_quality.value;
748 for (i = -16;i < 16;i += inc)
750 for (j = -16;j < 16;j += inc)
752 for (k = -24;k < 32;k += inc)
754 VectorSet(dir, i*8, j*8, k*8);
755 VectorNormalize(dir);
756 vel = lhrandom(50, 113);
757 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);
761 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);
763 else if (effectnameindex == EFFECT_TE_TEI_G3)
764 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);
765 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
767 if (cl_particles_smoke.integer)
769 count *= 0.25f * cl_particles_quality.value;
771 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);
774 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
776 CL_ParticleExplosion(center);
777 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);
779 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
782 if (cl_stainmaps.integer)
783 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
784 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
785 if (cl_particles_smoke.integer)
786 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
787 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);
788 if (cl_particles_sparks.integer)
789 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
790 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);
791 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);
793 else if (effectnameindex == EFFECT_EF_FLAME)
795 count *= 300 * cl_particles_quality.value;
797 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);
798 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);
800 else if (effectnameindex == EFFECT_EF_STARDUST)
802 count *= 200 * cl_particles_quality.value;
804 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);
805 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);
807 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
811 int smoke, blood, bubbles, r, color;
813 if (effectnameindex == EFFECT_TR_ROCKET)
814 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);
815 else if (effectnameindex == EFFECT_TR_VORESPIKE)
817 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
818 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);
820 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);
822 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
823 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);
825 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
828 VectorSubtract(originmaxs, originmins, dir);
829 len = VectorNormalizeLength(dir);
830 dec = -ent->persistent.trail_time;
831 ent->persistent.trail_time += len;
832 if (ent->persistent.trail_time < 0.01f)
835 // if we skip out, leave it reset
836 ent->persistent.trail_time = 0.0f;
838 // advance into this frame to reach the first puff location
839 VectorMA(originmins, dec, dir, pos);
842 smoke = cl_particles.integer && cl_particles_smoke.integer;
843 blood = cl_particles.integer && cl_particles_blood.integer;
844 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
845 qd = 1.0f / cl_particles_quality.value;
852 if (effectnameindex == EFFECT_TR_BLOOD)
854 if (cl_particles_quake.integer)
856 color = particlepalette[67 + (rand()&3)];
857 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);
862 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);
865 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
867 if (cl_particles_quake.integer)
870 color = particlepalette[67 + (rand()&3)];
871 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);
876 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);
882 if (effectnameindex == EFFECT_TR_ROCKET)
884 if (cl_particles_quake.integer)
887 color = particlepalette[ramp3[r]];
888 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);
892 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);
893 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);
896 else if (effectnameindex == EFFECT_TR_GRENADE)
898 if (cl_particles_quake.integer)
901 color = particlepalette[ramp3[r]];
902 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);
906 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);
909 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
911 if (cl_particles_quake.integer)
914 color = particlepalette[52 + (rand()&7)];
915 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);
916 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);
918 else if (gamemode == GAME_GOODVSBAD2)
921 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);
925 color = particlepalette[20 + (rand()&7)];
926 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);
929 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
931 if (cl_particles_quake.integer)
934 color = particlepalette[230 + (rand()&7)];
935 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);
936 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);
940 color = particlepalette[226 + (rand()&7)];
941 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);
944 else if (effectnameindex == EFFECT_TR_VORESPIKE)
946 if (cl_particles_quake.integer)
948 color = particlepalette[152 + (rand()&3)];
949 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);
951 else if (gamemode == GAME_GOODVSBAD2)
954 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);
956 else if (gamemode == GAME_PRYDON)
959 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);
962 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);
964 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
967 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);
969 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
972 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);
974 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
975 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);
979 if (effectnameindex == EFFECT_TR_ROCKET)
980 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);
981 else if (effectnameindex == EFFECT_TR_GRENADE)
982 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);
984 // advance to next time and position
987 VectorMA (pos, dec, dir, pos);
989 ent->persistent.trail_time = len;
991 else if (developer.integer >= 1)
992 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
995 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)
998 qboolean found = false;
999 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1000 return; // invalid effect index
1001 if (!particleeffectname[effectnameindex][0])
1002 return; // no such effect
1003 VectorLerp(originmins, 0.5, originmaxs, center);
1004 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1006 int effectinfoindex;
1009 particleeffectinfo_t *info;
1011 vec3_t centervelocity;
1015 qboolean underwater;
1016 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1017 VectorLerp(originmins, 0.5, originmaxs, center);
1018 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1019 supercontents = CL_PointSuperContents(center);
1020 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1021 VectorSubtract(originmaxs, originmins, traildir);
1022 VectorNormalize(traildir);
1023 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1025 if (info->effectnameindex == effectnameindex)
1028 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1030 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1033 // spawn a dlight if requested
1034 if (info->lightradiusstart > 0)
1036 matrix4x4_t tempmatrix;
1037 if (info->trailspacing > 0)
1038 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1040 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1041 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);
1046 if (info->tex[1] > info->tex[0])
1048 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1049 tex = min(tex, info->tex[1] - 1);
1051 if (info->particletype == pt_decal)
1052 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]);
1053 else if (info->particletype == pt_beam)
1054 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);
1057 if (!cl_particles.integer)
1059 switch (info->particletype)
1061 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1062 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1063 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1064 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1067 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1068 VectorCopy(originmins, trailpos);
1069 for (;info->particleaccumulator > 0;info->particleaccumulator--)
1071 if (info->tex[1] > info->tex[0])
1073 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1074 tex = min(tex, info->tex[1] - 1);
1076 if (info->trailspacing <= 0)
1078 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1079 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1080 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1083 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);
1084 if (info->trailspacing > 0)
1085 VectorMA(trailpos, info->trailspacing, traildir, trailpos);
1092 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor);
1100 void CL_EntityParticles (const entity_t *ent)
1103 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1104 static vec3_t avelocities[NUMVERTEXNORMALS];
1105 if (!cl_particles.integer) return;
1107 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1109 if (!avelocities[0][0])
1110 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1111 avelocities[0][i] = lhrandom(0, 2.55);
1113 for (i = 0;i < NUMVERTEXNORMALS;i++)
1115 yaw = cl.time * avelocities[i][0];
1116 pitch = cl.time * avelocities[i][1];
1117 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1118 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1119 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1120 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);
1125 void CL_ReadPointFile_f (void)
1127 vec3_t org, leakorg;
1129 char *pointfile = NULL, *pointfilepos, *t, tchar;
1130 char name[MAX_OSPATH];
1135 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1136 strlcat (name, ".pts", sizeof (name));
1137 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1140 Con_Printf("Could not open %s\n", name);
1144 Con_Printf("Reading %s...\n", name);
1145 VectorClear(leakorg);
1148 pointfilepos = pointfile;
1149 while (*pointfilepos)
1151 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1156 while (*t && *t != '\n' && *t != '\r')
1160 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1166 VectorCopy(org, leakorg);
1169 if (cl.num_particles < cl.max_particles - 3)
1172 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);
1175 Mem_Free(pointfile);
1176 VectorCopy(leakorg, org);
1177 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1179 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);
1180 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);
1181 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);
1186 CL_ParseParticleEffect
1188 Parse an effect out of the server message
1191 void CL_ParseParticleEffect (void)
1194 int i, count, msgcount, color;
1196 MSG_ReadVector(org, cls.protocol);
1197 for (i=0 ; i<3 ; i++)
1198 dir[i] = MSG_ReadChar ();
1199 msgcount = MSG_ReadByte ();
1200 color = MSG_ReadByte ();
1202 if (msgcount == 255)
1207 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1212 CL_ParticleExplosion
1216 void CL_ParticleExplosion (const vec3_t org)
1222 if (cl_stainmaps.integer)
1223 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1224 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1226 if (cl_particles_quake.integer)
1228 for (i = 0;i < 1024;i++)
1234 color = particlepalette[ramp1[r]];
1235 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);
1239 color = particlepalette[ramp2[r]];
1240 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);
1246 i = CL_PointSuperContents(org);
1247 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1249 if (cl_particles.integer && cl_particles_bubbles.integer)
1250 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1251 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);
1255 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1257 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1259 for (i = 0;i < 32;i++)
1263 for (k = 0;k < 16;k++)
1265 v[0] = org[0] + lhrandom(-48, 48);
1266 v[1] = org[1] + lhrandom(-48, 48);
1267 v[2] = org[2] + lhrandom(-48, 48);
1268 trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
1269 if (trace.fraction >= 0.1)
1272 VectorSubtract(trace.endpos, org, v2);
1273 VectorScale(v2, 2.0f, v2);
1274 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);
1278 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1279 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1280 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);
1284 if (cl_particles_explosions_shell.integer)
1285 R_NewExplosion(org);
1290 CL_ParticleExplosion2
1294 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1297 if (!cl_particles.integer) return;
1299 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1301 k = particlepalette[colorStart + (i % colorLength)];
1302 if (cl_particles_quake.integer)
1303 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);
1305 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);
1309 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1311 if (cl_particles_sparks.integer)
1313 sparkcount *= cl_particles_quality.value;
1314 while(sparkcount-- > 0)
1315 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);
1317 if (cl_particles_smoke.integer)
1319 smokecount *= cl_particles_quality.value;
1320 while(smokecount-- > 0)
1321 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);
1325 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)
1328 if (!cl_particles.integer) return;
1330 count = (int)(count * cl_particles_quality.value);
1333 k = particlepalette[colorbase + (rand()&3)];
1334 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);
1338 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1341 float z, minz, maxz;
1343 if (!cl_particles.integer) return;
1344 if (dir[2] < 0) // falling
1349 minz = z - fabs(dir[2]) * 0.1;
1350 maxz = z + fabs(dir[2]) * 0.1;
1351 minz = bound(mins[2], minz, maxs[2]);
1352 maxz = bound(mins[2], maxz, maxs[2]);
1354 count = (int)(count * cl_particles_quality.value);
1359 count *= 4; // ick, this should be in the mod or maps?
1363 k = particlepalette[colorbase + (rand()&3)];
1364 if (gamemode == GAME_GOODVSBAD2)
1365 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);
1367 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);
1373 k = particlepalette[colorbase + (rand()&3)];
1374 if (gamemode == GAME_GOODVSBAD2)
1375 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);
1377 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);
1379 VectorCopy(p->vel, p->relativedirection);
1383 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1392 void CL_MoveParticles (void)
1395 int i, maxparticle, j, a, content;
1396 float gravity, dvel, bloodwaterfade, frametime, f, dist, org[3], oldorg[3];
1400 // LordHavoc: early out condition
1401 if (!cl.num_particles)
1403 cl.free_particle = 0;
1407 frametime = cl.time - cl.oldtime;
1408 gravity = frametime * sv_gravity.value;
1409 dvel = 1+4*frametime;
1410 bloodwaterfade = max(cl_particles_blood_alpha.value, 0.01f) * frametime * 128.0f;
1414 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1421 p->alpha -= p->alphafade * frametime;
1429 if (p->type->orientation != PARTICLE_BEAM)
1431 VectorCopy(p->org, oldorg);
1432 VectorMA(p->org, frametime, p->vel, p->org);
1433 VectorCopy(p->org, org);
1436 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);
1437 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1438 // or if the trace hit something flagged as NOIMPACT
1439 // then remove the particle
1440 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1445 // react if the particle hit something
1446 if (trace.fraction < 1)
1448 VectorCopy(trace.endpos, p->org);
1449 if (p->type == particletype + pt_rain)
1451 // raindrop - splash on solid/water/slime/lava
1453 // convert from a raindrop particle to a rainsplash decal
1454 VectorCopy(trace.plane.normal, p->vel);
1455 VectorAdd(p->org, p->vel, p->org);
1456 p->type = particletype + pt_raindecal;
1457 p->texnum = tex_rainsplash[0];
1459 p->alphafade = p->alpha / 0.4;
1466 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);
1468 else if (p->type == particletype + pt_blood)
1470 // blood - splash on solid
1471 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1476 if (!cl_decals.integer)
1481 // convert from a blood particle to a blood decal
1482 VectorCopy(trace.plane.normal, p->vel);
1483 VectorAdd(p->org, p->vel, p->org);
1484 if (cl_stainmaps.integer)
1485 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)));
1487 p->type = particletype + pt_decal;
1488 p->texnum = tex_blooddecal[rand()&7];
1490 p->ownermodel = cl.entities[hitent].render.model;
1491 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1492 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1500 else if (p->bounce < 0)
1502 // bounce -1 means remove on impact
1508 // anything else - bounce off solid
1509 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1510 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1511 if (DotProduct(p->vel, p->vel) < 0.03)
1512 VectorClear(p->vel);
1516 p->vel[2] -= p->gravity * gravity;
1520 f = p->friction * frametime;
1521 if (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1524 VectorScale(p->vel, f, p->vel);
1528 if (p->type != particletype + pt_static)
1530 switch (p->type - particletype)
1532 case pt_entityparticle:
1533 // particle that removes itself after one rendered frame
1540 a = CL_PointSuperContents(p->org);
1541 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1543 p->size += frametime * 8;
1544 //p->alpha -= bloodwaterfade;
1547 p->vel[2] -= gravity;
1548 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1552 a = CL_PointSuperContents(p->org);
1553 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1560 a = CL_PointSuperContents(p->org);
1561 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1565 if (cl.time > p->time2)
1568 p->time2 = cl.time + (rand() & 3) * 0.1;
1569 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1570 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1571 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1573 a = CL_PointSuperContents(p->org);
1574 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1578 //p->size += frametime * 15;
1581 // FIXME: this has fairly wacky handling of alpha
1582 p->alphafade = cl.time > (p->time2 + cl_decals_time.value) ? (255 / cl_decals_fadetime.value) : 0;
1583 if (cl.entities[p->owner].render.model == p->ownermodel)
1585 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1586 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1592 a = (int)max(0, (cl.time - p->time2) * 40);
1594 p->texnum = tex_rainsplash[a];
1603 cl.num_particles = maxparticle + 1;
1604 cl.free_particle = 0;
1607 #define MAX_PARTICLETEXTURES 64
1608 // particletexture_t is a rectangle in the particlefonttexture
1609 typedef struct particletexture_s
1611 rtexture_t *texture;
1612 float s1, t1, s2, t2;
1616 static rtexturepool_t *particletexturepool;
1617 static rtexture_t *particlefonttexture;
1618 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1620 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1622 #define PARTICLETEXTURESIZE 64
1623 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1625 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1629 dz = 1 - (dx*dx+dy*dy);
1630 if (dz > 0) // it does hit the sphere
1634 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1635 VectorNormalize(normal);
1636 dot = DotProduct(normal, light);
1637 if (dot > 0.5) // interior reflection
1638 f += ((dot * 2) - 1);
1639 else if (dot < -0.5) // exterior reflection
1640 f += ((dot * -2) - 1);
1642 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1643 VectorNormalize(normal);
1644 dot = DotProduct(normal, light);
1645 if (dot > 0.5) // interior reflection
1646 f += ((dot * 2) - 1);
1647 else if (dot < -0.5) // exterior reflection
1648 f += ((dot * -2) - 1);
1650 f += 16; // just to give it a haze so you can see the outline
1651 f = bound(0, f, 255);
1652 return (unsigned char) f;
1658 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1660 int basex, basey, y;
1661 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1662 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1663 particletexture[texnum].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1664 particletexture[texnum].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1665 particletexture[texnum].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1666 particletexture[texnum].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1667 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1668 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1671 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1674 float cx, cy, dx, dy, f, iradius;
1676 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1677 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1678 iradius = 1.0f / radius;
1679 alpha *= (1.0f / 255.0f);
1680 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1682 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1686 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1689 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1690 d[0] += (int)(f * (red - d[0]));
1691 d[1] += (int)(f * (green - d[1]));
1692 d[2] += (int)(f * (blue - d[2]));
1698 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1701 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1703 data[0] = bound(minr, data[0], maxr);
1704 data[1] = bound(ming, data[1], maxg);
1705 data[2] = bound(minb, data[2], maxb);
1709 void particletextureinvert(unsigned char *data)
1712 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1714 data[0] = 255 - data[0];
1715 data[1] = 255 - data[1];
1716 data[2] = 255 - data[2];
1720 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1721 static void R_InitBloodTextures (unsigned char *particletexturedata)
1724 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1727 for (i = 0;i < 8;i++)
1729 memset(&data[0][0][0], 255, sizeof(data));
1730 for (k = 0;k < 24;k++)
1731 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1732 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1733 particletextureinvert(&data[0][0][0]);
1734 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1738 for (i = 0;i < 8;i++)
1740 memset(&data[0][0][0], 255, sizeof(data));
1742 for (j = 1;j < 10;j++)
1743 for (k = min(j, m - 1);k < m;k++)
1744 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1745 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1746 particletextureinvert(&data[0][0][0]);
1747 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1752 static void R_InitParticleTexture (void)
1754 int x, y, d, i, k, m;
1755 float dx, dy, radius, f, f2;
1756 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4], noise3[64][64], data2[64][16][4];
1758 unsigned char *particletexturedata;
1760 // a note: decals need to modulate (multiply) the background color to
1761 // properly darken it (stain), and they need to be able to alpha fade,
1762 // this is a very difficult challenge because it means fading to white
1763 // (no change to background) rather than black (darkening everything
1764 // behind the whole decal polygon), and to accomplish this the texture is
1765 // inverted (dark red blood on white background becomes brilliant cyan
1766 // and white on black background) so we can alpha fade it to black, then
1767 // we invert it again during the blendfunc to make it work...
1769 particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1770 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1773 for (i = 0;i < 8;i++)
1775 memset(&data[0][0][0], 255, sizeof(data));
1778 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1780 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1781 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1783 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1785 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1786 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1788 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1789 d = (noise2[y][x] - 128) * 3 + 192;
1791 d = (int)(d * (1-(dx*dx+dy*dy)));
1792 d = (d * noise1[y][x]) >> 7;
1793 d = bound(0, d, 255);
1794 data[y][x][3] = (unsigned char) d;
1801 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1805 for (i = 0;i < 16;i++)
1807 memset(&data[0][0][0], 255, sizeof(data));
1808 radius = i * 3.0f / 4.0f / 16.0f;
1809 f2 = 255.0f * ((15.0f - i) / 15.0f);
1810 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1812 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1813 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1815 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1816 f = f2 * (1.0 - 4.0f * fabs(radius - sqrt(dx*dx+dy*dy)));
1817 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1820 setuptex(tex_rainsplash[i], &data[0][0][0], particletexturedata);
1824 memset(&data[0][0][0], 255, sizeof(data));
1825 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1827 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1828 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1830 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1831 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1832 d = bound(0, d, 255);
1833 data[y][x][3] = (unsigned char) d;
1836 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1839 memset(&data[0][0][0], 255, sizeof(data));
1840 light[0] = 1;light[1] = 1;light[2] = 1;
1841 VectorNormalize(light);
1842 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1844 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1845 // stretch upper half of bubble by +50% and shrink lower half by -50%
1846 // (this gives an elongated teardrop shape)
1848 dy = (dy - 0.5f) * 2.0f;
1850 dy = (dy - 0.5f) / 1.5f;
1851 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1853 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1854 // shrink bubble width to half
1856 data[y][x][3] = shadebubble(dx, dy, light);
1859 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1862 memset(&data[0][0][0], 255, sizeof(data));
1863 light[0] = 1;light[1] = 1;light[2] = 1;
1864 VectorNormalize(light);
1865 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1867 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1868 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1870 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1871 data[y][x][3] = shadebubble(dx, dy, light);
1874 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1876 // Blood particles and blood decals
1877 R_InitBloodTextures (particletexturedata);
1880 for (i = 0;i < 8;i++)
1882 memset(&data[0][0][0], 255, sizeof(data));
1883 for (k = 0;k < 12;k++)
1884 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1885 for (k = 0;k < 3;k++)
1886 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1887 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1888 particletextureinvert(&data[0][0][0]);
1889 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1893 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1896 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1897 if (!particlefonttexture)
1898 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1899 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1900 particletexture[i].texture = particlefonttexture;
1903 fractalnoise(&noise3[0][0], 64, 4);
1905 for (y = 0;y < 64;y++)
1907 dy = (y - 0.5f*64) / (64*0.5f-1);
1908 for (x = 0;x < 16;x++)
1910 dx = (x - 0.5f*16) / (16*0.5f-2);
1911 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1912 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1913 data2[y][x][3] = 255;
1918 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1921 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1922 if (!particletexture[tex_beam].texture)
1923 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1924 particletexture[tex_beam].s1 = 0;
1925 particletexture[tex_beam].t1 = 0;
1926 particletexture[tex_beam].s2 = 1;
1927 particletexture[tex_beam].t2 = 1;
1928 Mem_Free(particletexturedata);
1931 static void r_part_start(void)
1933 particletexturepool = R_AllocTexturePool();
1934 R_InitParticleTexture ();
1935 CL_Particles_LoadEffectInfo();
1938 static void r_part_shutdown(void)
1940 R_FreeTexturePool(&particletexturepool);
1943 static void r_part_newmap(void)
1947 void R_Particles_Init (void)
1949 Cvar_RegisterVariable(&r_drawparticles);
1950 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1953 float particle_vertex3f[12], particle_texcoord2f[8];
1955 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight)
1957 const particle_t *p = cl.particles + surfacenumber;
1960 float org[3], up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
1961 particletexture_t *tex;
1963 VectorCopy(p->org, org);
1965 blendmode = p->type->blendmode;
1966 tex = &particletexture[p->texnum];
1967 cr = p->color[0] * (1.0f / 255.0f);
1968 cg = p->color[1] * (1.0f / 255.0f);
1969 cb = p->color[2] * (1.0f / 255.0f);
1970 ca = p->alpha * (1.0f / 255.0f);
1971 if (blendmode == PBLEND_MOD)
1981 ca /= cl_particles_quality.value;
1982 if (p->type->lighting)
1984 float ambient[3], diffuse[3], diffusenormal[3];
1985 R_CompleteLightPoint(ambient, diffuse, diffusenormal, org, true);
1986 cr *= (ambient[0] + 0.5 * diffuse[0]);
1987 cg *= (ambient[1] + 0.5 * diffuse[1]);
1988 cb *= (ambient[2] + 0.5 * diffuse[2]);
1992 fog = VERTEXFOGTABLE(VectorDistance(org, r_vieworigin));
1997 if (blendmode == PBLEND_ALPHA)
1999 cr += fogcolor[0] * fog;
2000 cg += fogcolor[1] * fog;
2001 cb += fogcolor[2] * fog;
2005 R_Mesh_Matrix(&identitymatrix);
2007 R_Mesh_VertexPointer(particle_vertex3f);
2008 R_Mesh_ColorPointer(NULL);
2009 memset(&m, 0, sizeof(m));
2010 m.tex[0] = R_GetTexture(tex->texture);
2011 m.pointer_texcoord[0] = particle_texcoord2f;
2012 R_Mesh_TextureState(&m);
2014 GL_Color(cr, cg, cb, ca);
2016 if (blendmode == PBLEND_ALPHA)
2017 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2018 else if (blendmode == PBLEND_ADD)
2019 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2020 else //if (blendmode == PBLEND_MOD)
2021 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2022 GL_DepthMask(false);
2024 size = p->size * cl_particles_size.value;
2025 if (p->type->orientation == PARTICLE_BILLBOARD || p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2027 if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2030 if (DotProduct(p->vel, r_vieworigin) > DotProduct(p->vel, org))
2032 VectorNegate(p->vel, v);
2033 VectorVectors(v, right, up);
2036 VectorVectors(p->vel, right, up);
2037 VectorScale(right, size, right);
2038 VectorScale(up, size, up);
2042 VectorScale(r_viewleft, -size, right);
2043 VectorScale(r_viewup, size, up);
2045 particle_vertex3f[ 0] = org[0] - right[0] - up[0];
2046 particle_vertex3f[ 1] = org[1] - right[1] - up[1];
2047 particle_vertex3f[ 2] = org[2] - right[2] - up[2];
2048 particle_vertex3f[ 3] = org[0] - right[0] + up[0];
2049 particle_vertex3f[ 4] = org[1] - right[1] + up[1];
2050 particle_vertex3f[ 5] = org[2] - right[2] + up[2];
2051 particle_vertex3f[ 6] = org[0] + right[0] + up[0];
2052 particle_vertex3f[ 7] = org[1] + right[1] + up[1];
2053 particle_vertex3f[ 8] = org[2] + right[2] + up[2];
2054 particle_vertex3f[ 9] = org[0] + right[0] - up[0];
2055 particle_vertex3f[10] = org[1] + right[1] - up[1];
2056 particle_vertex3f[11] = org[2] + right[2] - up[2];
2057 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2058 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2059 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2060 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2062 else if (p->type->orientation == PARTICLE_SPARK)
2064 VectorMA(p->org, -0.02, p->vel, v);
2065 VectorMA(p->org, 0.02, p->vel, up2);
2066 R_CalcBeam_Vertex3f(particle_vertex3f, v, up2, size);
2067 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2068 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2069 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2070 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2072 else if (p->type->orientation == PARTICLE_BEAM)
2074 R_CalcBeam_Vertex3f(particle_vertex3f, p->org, p->vel, size);
2075 VectorSubtract(p->vel, p->org, up);
2076 VectorNormalize(up);
2077 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2078 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2079 particle_texcoord2f[0] = 1;particle_texcoord2f[1] = v[0];
2080 particle_texcoord2f[2] = 0;particle_texcoord2f[3] = v[0];
2081 particle_texcoord2f[4] = 0;particle_texcoord2f[5] = v[1];
2082 particle_texcoord2f[6] = 1;particle_texcoord2f[7] = v[1];
2086 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2090 R_Mesh_Draw(0, 4, 2, polygonelements);
2093 void R_DrawParticles (void)
2096 float minparticledist;
2099 // LordHavoc: early out conditions
2100 if ((!cl.num_particles) || (!r_drawparticles.integer))
2103 minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
2105 // LordHavoc: only render if not too close
2106 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2110 renderstats.particles++;
2111 if (DotProduct(p->org, r_viewforward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2113 if (p->type == particletype + pt_decal)
2114 R_DrawParticle_TransparentCallback(0, i, 0);
2116 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);