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, plus size increase over time
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 = 32;
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], "sizeincrease")) {readfloat(info->size[2]);}
300 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
301 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
302 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
303 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
304 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
305 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
306 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
307 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
308 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
309 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
310 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
311 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
312 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
313 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
314 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
315 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
316 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
317 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
318 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
319 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
321 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
330 int CL_ParticleEffectIndexForName(const char *name)
333 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
334 if (!strcmp(particleeffectname[i], name))
339 const char *CL_ParticleEffectNameForIndex(int i)
341 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
343 return particleeffectname[i];
346 // MUST match effectnameindex_t in client.h
347 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 psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter)
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->sizeincrease = psizeincrease;
475 part->alpha = palpha;
476 part->alphafade = palphafade;
477 part->gravity = pgravity;
478 part->bounce = pbounce;
480 part->org[0] = px + originjitter * v[0];
481 part->org[1] = py + originjitter * v[1];
482 part->org[2] = pz + originjitter * v[2];
483 part->vel[0] = pvx + velocityjitter * v[0];
484 part->vel[1] = pvy + velocityjitter * v[1];
485 part->vel[2] = pvz + velocityjitter * v[2];
487 part->airfriction = pairfriction;
488 part->liquidfriction = pliquidfriction;
492 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
495 if (!cl_decals.integer)
497 p = particle(particletype + pt_decal, color1, color2, texnum, size, 0, alpha, 0, 0, 0, org[0] + normal[0], org[1] + normal[1], org[2] + normal[2], normal[0], normal[1], normal[2], 0, 0, 0, 0);
502 p->ownermodel = cl.entities[p->owner].render.model;
503 VectorAdd(org, normal, p->org);
504 VectorCopy(normal, p->vel);
505 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
506 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
507 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
511 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
514 float bestfrac, bestorg[3], bestnormal[3];
516 int besthitent = 0, hitent;
519 for (i = 0;i < 32;i++)
522 VectorMA(org, maxdist, org2, org2);
523 trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, false);
524 // take the closest trace result that doesn't end up hitting a NOMARKS
525 // surface (sky for example)
526 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
528 bestfrac = trace.fraction;
530 VectorCopy(trace.endpos, bestorg);
531 VectorCopy(trace.plane.normal, bestnormal);
535 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
538 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount);
539 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)
542 matrix4x4_t tempmatrix;
543 VectorLerp(originmins, 0.5, originmaxs, center);
544 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
545 if (effectnameindex == EFFECT_SVC_PARTICLE)
547 if (cl_particles.integer)
549 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
551 CL_ParticleExplosion(center);
552 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
553 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
556 count *= cl_particles_quality.value;
557 for (;count > 0;count--)
559 int k = particlepalette[palettecolor + (rand()&7)];
560 if (cl_particles_quake.integer)
561 particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 0, lhrandom(51, 255), 512, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0);
562 else if (gamemode == GAME_GOODVSBAD2)
563 particle(particletype + pt_alphastatic, k, k, tex_particle, 5, 0, 255, 300, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 10);
565 particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 15);
570 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
571 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
572 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
573 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
574 else if (effectnameindex == EFFECT_TE_SPIKE)
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);
590 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
592 if (cl_particles_bulletimpacts.integer)
594 if (cl_particles_quake.integer)
596 if (cl_particles_smoke.integer)
597 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
600 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
603 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
604 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
605 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);
607 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
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);
623 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
625 if (cl_particles_bulletimpacts.integer)
627 if (cl_particles_quake.integer)
629 if (cl_particles_smoke.integer)
630 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
633 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
636 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
637 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
638 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);
640 else if (effectnameindex == EFFECT_TE_BLOOD)
642 if (!cl_particles_blood.integer)
644 if (cl_particles_quake.integer)
645 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
648 static double bloodaccumulator = 0;
649 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
650 for (;bloodaccumulator > 0;bloodaccumulator--)
651 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 0, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
654 else if (effectnameindex == EFFECT_TE_SPARK)
655 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count, 0);
656 else if (effectnameindex == EFFECT_TE_PLASMABURN)
658 // plasma scorch mark
659 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
660 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
661 CL_AllocDlight(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
663 else if (effectnameindex == EFFECT_TE_GUNSHOT)
665 if (cl_particles_bulletimpacts.integer)
667 if (cl_particles_quake.integer)
668 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
670 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
673 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
674 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
676 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
678 if (cl_particles_bulletimpacts.integer)
680 if (cl_particles_quake.integer)
681 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
683 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
686 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
687 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
688 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);
690 else if (effectnameindex == EFFECT_TE_EXPLOSION)
692 CL_ParticleExplosion(center);
693 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);
695 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
697 CL_ParticleExplosion(center);
698 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);
700 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
702 if (cl_particles_quake.integer)
705 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
708 particle(particletype + pt_static, particlepalette[66], particlepalette[71], tex_particle, 1, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256);
710 particle(particletype + pt_static, particlepalette[150], particlepalette[155], tex_particle, 1, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0);
714 CL_ParticleExplosion(center);
715 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);
717 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
718 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);
719 else if (effectnameindex == EFFECT_TE_FLAMEJET)
721 count *= cl_particles_quality.value;
723 particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128);
725 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
727 float i, j, inc, vel;
730 inc = 8 / cl_particles_quality.value;
731 for (i = -128;i < 128;i += inc)
733 for (j = -128;j < 128;j += inc)
735 dir[0] = j + lhrandom(0, inc);
736 dir[1] = i + lhrandom(0, inc);
738 org[0] = center[0] + dir[0];
739 org[1] = center[1] + dir[1];
740 org[2] = center[2] + lhrandom(0, 64);
741 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
742 particle(particletype + pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1, 0, inc * lhrandom(24, 32), inc * 12, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
746 else if (effectnameindex == EFFECT_TE_TELEPORT)
748 float i, j, k, inc, vel;
751 inc = 8 / cl_particles_quality.value;
752 for (i = -16;i < 16;i += inc)
754 for (j = -16;j < 16;j += inc)
756 for (k = -24;k < 32;k += inc)
758 VectorSet(dir, i*8, j*8, k*8);
759 VectorNormalize(dir);
760 vel = lhrandom(50, 113);
761 particle(particletype + pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
765 particle(particletype + pt_static, particlepalette[14], particlepalette[14], tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0);
766 CL_AllocDlight(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
768 else if (effectnameindex == EFFECT_TE_TEI_G3)
769 particle(particletype + pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0);
770 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
772 if (cl_particles_smoke.integer)
774 count *= 0.25f * cl_particles_quality.value;
776 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f);
779 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
781 CL_ParticleExplosion(center);
782 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);
784 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
787 if (cl_stainmaps.integer)
788 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
789 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
790 if (cl_particles_smoke.integer)
791 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
792 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155);
793 if (cl_particles_sparks.integer)
794 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
795 particle(particletype + pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465);
796 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);
798 else if (effectnameindex == EFFECT_EF_FLAME)
800 count *= 300 * cl_particles_quality.value;
802 particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128);
803 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);
805 else if (effectnameindex == EFFECT_EF_STARDUST)
807 count *= 200 * cl_particles_quality.value;
809 particle(particletype + pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128);
810 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);
812 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
816 int smoke, blood, bubbles, r, color;
818 if (effectnameindex == EFFECT_TR_ROCKET)
819 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);
820 else if (effectnameindex == EFFECT_TR_VORESPIKE)
822 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
823 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);
825 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);
827 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
828 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);
830 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
833 VectorSubtract(originmaxs, originmins, dir);
834 len = VectorNormalizeLength(dir);
835 dec = -ent->persistent.trail_time;
836 ent->persistent.trail_time += len;
837 if (ent->persistent.trail_time < 0.01f)
840 // if we skip out, leave it reset
841 ent->persistent.trail_time = 0.0f;
843 // advance into this frame to reach the first puff location
844 VectorMA(originmins, dec, dir, pos);
847 smoke = cl_particles.integer && cl_particles_smoke.integer;
848 blood = cl_particles.integer && cl_particles_blood.integer;
849 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
850 qd = 1.0f / cl_particles_quality.value;
857 if (effectnameindex == EFFECT_TR_BLOOD)
859 if (cl_particles_quake.integer)
861 color = particlepalette[67 + (rand()&3)];
862 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
867 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
870 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
872 if (cl_particles_quake.integer)
875 color = particlepalette[67 + (rand()&3)];
876 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
881 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
887 if (effectnameindex == EFFECT_TR_ROCKET)
889 if (cl_particles_quake.integer)
892 color = particlepalette[ramp3[r]];
893 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
897 particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
898 particle(particletype + pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20);
901 else if (effectnameindex == EFFECT_TR_GRENADE)
903 if (cl_particles_quake.integer)
906 color = particlepalette[ramp3[r]];
907 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
911 particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*50, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
914 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
916 if (cl_particles_quake.integer)
919 color = particlepalette[52 + (rand()&7)];
920 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
921 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
923 else if (gamemode == GAME_GOODVSBAD2)
926 particle(particletype + pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
930 color = particlepalette[20 + (rand()&7)];
931 particle(particletype + pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
934 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
936 if (cl_particles_quake.integer)
939 color = particlepalette[230 + (rand()&7)];
940 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
941 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
945 color = particlepalette[226 + (rand()&7)];
946 particle(particletype + pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
949 else if (effectnameindex == EFFECT_TR_VORESPIKE)
951 if (cl_particles_quake.integer)
953 color = particlepalette[152 + (rand()&3)];
954 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0);
956 else if (gamemode == GAME_GOODVSBAD2)
959 particle(particletype + pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
961 else if (gamemode == GAME_PRYDON)
964 particle(particletype + pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
967 particle(particletype + pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
969 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
972 particle(particletype + pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4);
974 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
977 particle(particletype + pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16);
979 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
980 particle(particletype + pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
984 if (effectnameindex == EFFECT_TR_ROCKET)
985 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
986 else if (effectnameindex == EFFECT_TR_GRENADE)
987 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
989 // advance to next time and position
992 VectorMA (pos, dec, dir, pos);
994 ent->persistent.trail_time = len;
996 else if (developer.integer >= 1)
997 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1000 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)
1003 qboolean found = false;
1004 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1005 return; // invalid effect index
1006 if (!particleeffectname[effectnameindex][0])
1007 return; // no such effect
1008 VectorLerp(originmins, 0.5, originmaxs, center);
1009 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1011 int effectinfoindex;
1014 particleeffectinfo_t *info;
1016 vec3_t centervelocity;
1022 qboolean underwater;
1023 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1024 VectorLerp(originmins, 0.5, originmaxs, center);
1025 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1026 supercontents = CL_PointSuperContents(center);
1027 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1028 VectorSubtract(originmaxs, originmins, traildir);
1029 traillen = VectorLength(traildir);
1030 VectorNormalize(traildir);
1031 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1033 if (info->effectnameindex == effectnameindex)
1036 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1038 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1041 // spawn a dlight if requested
1042 if (info->lightradiusstart > 0)
1044 matrix4x4_t tempmatrix;
1045 if (info->trailspacing > 0)
1046 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1048 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1049 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);
1054 if (info->tex[1] > info->tex[0])
1056 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1057 tex = min(tex, info->tex[1] - 1);
1059 if (info->particletype == pt_decal)
1060 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]);
1061 else if (info->particletype == pt_beam)
1062 particle(particletype + info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0);
1065 if (!cl_particles.integer)
1067 switch (info->particletype)
1069 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1070 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1071 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1072 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1075 VectorCopy(originmins, trailpos);
1076 if (info->trailspacing > 0)
1078 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1079 trailstep = info->trailspacing / cl_particles_quality.value;
1083 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1086 for (;info->particleaccumulator > 0;info->particleaccumulator--)
1088 if (info->tex[1] > info->tex[0])
1090 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1091 tex = min(tex, info->tex[1] - 1);
1095 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1096 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1097 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1100 particle(particletype + info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0);
1102 VectorMA(trailpos, trailstep, traildir, trailpos);
1109 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor);
1117 void CL_EntityParticles (const entity_t *ent)
1120 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1121 static vec3_t avelocities[NUMVERTEXNORMALS];
1122 if (!cl_particles.integer) return;
1124 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1126 if (!avelocities[0][0])
1127 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1128 avelocities[0][i] = lhrandom(0, 2.55);
1130 for (i = 0;i < NUMVERTEXNORMALS;i++)
1132 yaw = cl.time * avelocities[i][0];
1133 pitch = cl.time * avelocities[i][1];
1134 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1135 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1136 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1137 particle(particletype + pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0);
1142 void CL_ReadPointFile_f (void)
1144 vec3_t org, leakorg;
1146 char *pointfile = NULL, *pointfilepos, *t, tchar;
1147 char name[MAX_OSPATH];
1152 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1153 strlcat (name, ".pts", sizeof (name));
1154 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1157 Con_Printf("Could not open %s\n", name);
1161 Con_Printf("Reading %s...\n", name);
1162 VectorClear(leakorg);
1165 pointfilepos = pointfile;
1166 while (*pointfilepos)
1168 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1173 while (*t && *t != '\n' && *t != '\r')
1177 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1183 VectorCopy(org, leakorg);
1186 if (cl.num_particles < cl.max_particles - 3)
1189 particle(particletype + pt_static, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0);
1192 Mem_Free(pointfile);
1193 VectorCopy(leakorg, org);
1194 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1196 particle(particletype + pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0);
1197 particle(particletype + pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0);
1198 particle(particletype + pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0);
1203 CL_ParseParticleEffect
1205 Parse an effect out of the server message
1208 void CL_ParseParticleEffect (void)
1211 int i, count, msgcount, color;
1213 MSG_ReadVector(org, cls.protocol);
1214 for (i=0 ; i<3 ; i++)
1215 dir[i] = MSG_ReadChar ();
1216 msgcount = MSG_ReadByte ();
1217 color = MSG_ReadByte ();
1219 if (msgcount == 255)
1224 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1229 CL_ParticleExplosion
1233 void CL_ParticleExplosion (const vec3_t org)
1239 if (cl_stainmaps.integer)
1240 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1241 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1243 if (cl_particles_quake.integer)
1245 for (i = 0;i < 1024;i++)
1251 color = particlepalette[ramp1[r]];
1252 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256);
1256 color = particlepalette[ramp2[r]];
1257 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256);
1263 i = CL_PointSuperContents(org);
1264 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1266 if (cl_particles.integer && cl_particles_bubbles.integer)
1267 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1268 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96);
1272 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1274 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1276 for (i = 0;i < 32;i++)
1280 for (k = 0;k < 16;k++)
1282 v[0] = org[0] + lhrandom(-48, 48);
1283 v[1] = org[1] + lhrandom(-48, 48);
1284 v[2] = org[2] + lhrandom(-48, 48);
1285 trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
1286 if (trace.fraction >= 0.1)
1289 VectorSubtract(trace.endpos, org, v2);
1290 VectorScale(v2, 2.0f, v2);
1291 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 12, 0, 32, 64, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0);
1295 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1296 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1297 particle(particletype + pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 1, 0, org[0], org[1], org[2], 0, 0, 80, 0.2, 0.8, 0, 256);
1301 if (cl_particles_explosions_shell.integer)
1302 R_NewExplosion(org);
1307 CL_ParticleExplosion2
1311 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1314 if (!cl_particles.integer) return;
1316 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1318 k = particlepalette[colorStart + (i % colorLength)];
1319 if (cl_particles_quake.integer)
1320 particle(particletype + pt_static, k, k, tex_particle, 1, 0, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 8, 256);
1322 particle(particletype + pt_static, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192);
1326 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1328 if (cl_particles_sparks.integer)
1330 sparkcount *= cl_particles_quality.value;
1331 while(sparkcount-- > 0)
1332 particle(particletype + pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.4f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + sv_gravity.value * 0.1, 0, 0, 0, 64);
1334 if (cl_particles_smoke.integer)
1336 smokecount *= cl_particles_quality.value;
1337 while(smokecount-- > 0)
1338 particle(particletype + pt_smoke, 0x101010, 0x202020, tex_smoke[rand()&7], 3, 0, 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, 0, 8);
1342 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)
1345 if (!cl_particles.integer) return;
1347 count = (int)(count * cl_particles_quality.value);
1350 k = particlepalette[colorbase + (rand()&3)];
1351 particle(particletype + pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel);
1355 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1358 float z, minz, maxz;
1360 if (!cl_particles.integer) return;
1361 if (dir[2] < 0) // falling
1366 minz = z - fabs(dir[2]) * 0.1;
1367 maxz = z + fabs(dir[2]) * 0.1;
1368 minz = bound(mins[2], minz, maxs[2]);
1369 maxz = bound(mins[2], maxz, maxs[2]);
1371 count = (int)(count * cl_particles_quality.value);
1376 count *= 4; // ick, this should be in the mod or maps?
1380 k = particlepalette[colorbase + (rand()&3)];
1381 if (gamemode == GAME_GOODVSBAD2)
1382 particle(particletype + pt_rain, k, k, tex_particle, 20, 0, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
1384 particle(particletype + pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
1390 k = particlepalette[colorbase + (rand()&3)];
1391 if (gamemode == GAME_GOODVSBAD2)
1392 p = particle(particletype + pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
1394 p = particle(particletype + pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
1396 VectorCopy(p->vel, p->relativedirection);
1400 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1409 void CL_MoveParticles (void)
1412 int i, maxparticle, j, a, content;
1413 float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
1414 particletype_t *decaltype, *bloodtype;
1418 // LordHavoc: early out condition
1419 if (!cl.num_particles)
1421 cl.free_particle = 0;
1425 frametime = bound(0, cl.time - cl.oldtime, 0.1);
1426 gravity = frametime * sv_gravity.value;
1427 dvel = 1+4*frametime;
1428 decalfade = frametime * 255 / cl_decals_fadetime.value;
1429 decaltype = particletype + pt_decal;
1430 bloodtype = particletype + pt_blood;
1434 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1438 if (cl.free_particle > i)
1439 cl.free_particle = i;
1444 // heavily optimized decal case
1445 if (p->type == decaltype)
1447 // FIXME: this has fairly wacky handling of alpha
1448 if (cl.time > p->time2 + cl_decals_time.value)
1450 p->alpha -= decalfade;
1454 if (cl.free_particle > i)
1455 cl.free_particle = i;
1461 if (cl.entities[p->owner].render.model == p->ownermodel)
1463 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1464 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1469 if (cl.free_particle > i)
1470 cl.free_particle = i;
1478 p->alpha -= p->alphafade * frametime;
1483 if (cl.free_particle > i)
1484 cl.free_particle = i;
1488 if (p->type->orientation != PARTICLE_BEAM)
1490 VectorCopy(p->org, oldorg);
1491 VectorMA(p->org, frametime, p->vel, p->org);
1492 VectorCopy(p->org, org);
1495 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);
1496 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1497 // or if the trace hit something flagged as NOIMPACT
1498 // then remove the particle
1499 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1504 // react if the particle hit something
1505 if (trace.fraction < 1)
1507 VectorCopy(trace.endpos, p->org);
1508 if (p->type == particletype + pt_rain)
1510 // raindrop - splash on solid/water/slime/lava
1512 // convert from a raindrop particle to a rainsplash decal
1513 VectorCopy(trace.plane.normal, p->vel);
1514 VectorAdd(p->org, p->vel, p->org);
1515 p->type = particletype + pt_raindecal;
1516 p->texnum = tex_rainsplash;
1518 p->alphafade = p->alpha / 0.4;
1521 p->liquidfriction = 0;
1524 p->sizeincrease = p->size * 16;
1527 particle(particletype + pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, 0, lhrandom(64, 255), 512, 1, 0, p->org[0], p->org[1], p->org[2], p->vel[0]*16, p->vel[1]*16, 32 + p->vel[2]*16, 0, 0, 0, 32);
1529 else if (p->type == bloodtype)
1531 // blood - splash on solid
1532 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1537 if (!cl_decals.integer)
1542 // convert from a blood particle to a blood decal
1543 VectorCopy(trace.plane.normal, p->vel);
1544 VectorAdd(p->org, p->vel, p->org);
1545 if (cl_stainmaps.integer)
1546 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)));
1548 p->type = particletype + pt_decal;
1549 p->texnum = tex_blooddecal[rand()&7];
1551 p->ownermodel = cl.entities[hitent].render.model;
1552 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1553 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1554 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1559 p->liquidfriction = 0;
1563 else if (p->bounce < 0)
1565 // bounce -1 means remove on impact
1571 // anything else - bounce off solid
1572 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1573 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1574 if (DotProduct(p->vel, p->vel) < 0.03)
1575 VectorClear(p->vel);
1579 p->vel[2] -= p->gravity * gravity;
1581 if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1583 f = 1.0f - min(p->liquidfriction * frametime, 1);
1584 VectorScale(p->vel, f, p->vel);
1586 else if (p->airfriction)
1588 f = 1.0f - min(p->airfriction * frametime, 1);
1589 VectorScale(p->vel, f, p->vel);
1593 if (p->type != particletype + pt_static)
1595 switch (p->type - particletype)
1597 case pt_entityparticle:
1598 // particle that removes itself after one rendered frame
1605 a = CL_PointSuperContents(p->org);
1606 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1608 p->size += frametime * 8;
1609 //p->alpha -= bloodwaterfade;
1612 p->vel[2] -= gravity;
1613 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1617 a = CL_PointSuperContents(p->org);
1618 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1625 a = CL_PointSuperContents(p->org);
1626 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1630 if (cl.time > p->time2)
1633 p->time2 = cl.time + (rand() & 3) * 0.1;
1634 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1635 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1636 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1638 a = CL_PointSuperContents(p->org);
1639 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1647 cl.num_particles = maxparticle + 1;
1650 #define MAX_PARTICLETEXTURES 64
1651 // particletexture_t is a rectangle in the particlefonttexture
1652 typedef struct particletexture_s
1654 rtexture_t *texture;
1655 float s1, t1, s2, t2;
1659 static rtexturepool_t *particletexturepool;
1660 static rtexture_t *particlefonttexture;
1661 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1663 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1665 #define PARTICLETEXTURESIZE 64
1666 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1668 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1672 dz = 1 - (dx*dx+dy*dy);
1673 if (dz > 0) // it does hit the sphere
1677 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1678 VectorNormalize(normal);
1679 dot = DotProduct(normal, light);
1680 if (dot > 0.5) // interior reflection
1681 f += ((dot * 2) - 1);
1682 else if (dot < -0.5) // exterior reflection
1683 f += ((dot * -2) - 1);
1685 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1686 VectorNormalize(normal);
1687 dot = DotProduct(normal, light);
1688 if (dot > 0.5) // interior reflection
1689 f += ((dot * 2) - 1);
1690 else if (dot < -0.5) // exterior reflection
1691 f += ((dot * -2) - 1);
1693 f += 16; // just to give it a haze so you can see the outline
1694 f = bound(0, f, 255);
1695 return (unsigned char) f;
1701 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1703 int basex, basey, y;
1704 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1705 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1706 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1707 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1710 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1713 float cx, cy, dx, dy, f, iradius;
1715 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1716 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1717 iradius = 1.0f / radius;
1718 alpha *= (1.0f / 255.0f);
1719 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1721 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1725 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1728 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1729 d[0] += (int)(f * (red - d[0]));
1730 d[1] += (int)(f * (green - d[1]));
1731 d[2] += (int)(f * (blue - d[2]));
1737 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1740 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1742 data[0] = bound(minr, data[0], maxr);
1743 data[1] = bound(ming, data[1], maxg);
1744 data[2] = bound(minb, data[2], maxb);
1748 void particletextureinvert(unsigned char *data)
1751 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1753 data[0] = 255 - data[0];
1754 data[1] = 255 - data[1];
1755 data[2] = 255 - data[2];
1759 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1760 static void R_InitBloodTextures (unsigned char *particletexturedata)
1763 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1766 for (i = 0;i < 8;i++)
1768 memset(&data[0][0][0], 255, sizeof(data));
1769 for (k = 0;k < 24;k++)
1770 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1771 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1772 particletextureinvert(&data[0][0][0]);
1773 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1777 for (i = 0;i < 8;i++)
1779 memset(&data[0][0][0], 255, sizeof(data));
1781 for (j = 1;j < 10;j++)
1782 for (k = min(j, m - 1);k < m;k++)
1783 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1784 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1785 particletextureinvert(&data[0][0][0]);
1786 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1791 //uncomment this to make engine save out particle font to a tga file when run
1792 //#define DUMPPARTICLEFONT
1794 static void R_InitParticleTexture (void)
1796 int x, y, d, i, k, m;
1800 // a note: decals need to modulate (multiply) the background color to
1801 // properly darken it (stain), and they need to be able to alpha fade,
1802 // this is a very difficult challenge because it means fading to white
1803 // (no change to background) rather than black (darkening everything
1804 // behind the whole decal polygon), and to accomplish this the texture is
1805 // inverted (dark red blood on white background becomes brilliant cyan
1806 // and white on black background) so we can alpha fade it to black, then
1807 // we invert it again during the blendfunc to make it work...
1809 #ifndef DUMPPARTICLEFONT
1810 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1811 if (!particlefonttexture)
1814 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1815 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1816 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1819 for (i = 0;i < 8;i++)
1821 memset(&data[0][0][0], 255, sizeof(data));
1824 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1826 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1827 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1829 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1831 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1832 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1834 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1835 d = (noise2[y][x] - 128) * 3 + 192;
1837 d = (int)(d * (1-(dx*dx+dy*dy)));
1838 d = (d * noise1[y][x]) >> 7;
1839 d = bound(0, d, 255);
1840 data[y][x][3] = (unsigned char) d;
1847 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1851 memset(&data[0][0][0], 255, sizeof(data));
1852 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1854 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1855 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1857 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1858 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1859 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1862 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1865 memset(&data[0][0][0], 255, sizeof(data));
1866 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1868 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1869 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1871 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1872 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1873 d = bound(0, d, 255);
1874 data[y][x][3] = (unsigned char) d;
1877 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1880 memset(&data[0][0][0], 255, sizeof(data));
1881 light[0] = 1;light[1] = 1;light[2] = 1;
1882 VectorNormalize(light);
1883 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1885 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1886 // stretch upper half of bubble by +50% and shrink lower half by -50%
1887 // (this gives an elongated teardrop shape)
1889 dy = (dy - 0.5f) * 2.0f;
1891 dy = (dy - 0.5f) / 1.5f;
1892 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1894 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1895 // shrink bubble width to half
1897 data[y][x][3] = shadebubble(dx, dy, light);
1900 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1903 memset(&data[0][0][0], 255, sizeof(data));
1904 light[0] = 1;light[1] = 1;light[2] = 1;
1905 VectorNormalize(light);
1906 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1908 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1909 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1911 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1912 data[y][x][3] = shadebubble(dx, dy, light);
1915 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1917 // Blood particles and blood decals
1918 R_InitBloodTextures (particletexturedata);
1921 for (i = 0;i < 8;i++)
1923 memset(&data[0][0][0], 255, sizeof(data));
1924 for (k = 0;k < 12;k++)
1925 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1926 for (k = 0;k < 3;k++)
1927 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1928 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1929 particletextureinvert(&data[0][0][0]);
1930 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1933 #ifdef DUMPPARTICLEFONT
1934 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1937 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1939 Mem_Free(particletexturedata);
1941 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1943 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1944 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1945 particletexture[i].texture = particlefonttexture;
1946 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1947 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1948 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1949 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1952 #ifndef DUMPPARTICLEFONT
1953 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1954 if (!particletexture[tex_beam].texture)
1957 unsigned char noise3[64][64], data2[64][16][4];
1959 fractalnoise(&noise3[0][0], 64, 4);
1961 for (y = 0;y < 64;y++)
1963 dy = (y - 0.5f*64) / (64*0.5f-1);
1964 for (x = 0;x < 16;x++)
1966 dx = (x - 0.5f*16) / (16*0.5f-2);
1967 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1968 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1969 data2[y][x][3] = 255;
1973 #ifdef DUMPPARTICLEFONT
1974 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1976 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1978 particletexture[tex_beam].s1 = 0;
1979 particletexture[tex_beam].t1 = 0;
1980 particletexture[tex_beam].s2 = 1;
1981 particletexture[tex_beam].t2 = 1;
1984 static void r_part_start(void)
1986 particletexturepool = R_AllocTexturePool();
1987 R_InitParticleTexture ();
1988 CL_Particles_LoadEffectInfo();
1991 static void r_part_shutdown(void)
1993 R_FreeTexturePool(&particletexturepool);
1996 static void r_part_newmap(void)
2000 #define BATCHSIZE 256
2001 int particle_element3i[BATCHSIZE*6];
2002 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2004 void R_Particles_Init (void)
2007 for (i = 0;i < BATCHSIZE;i++)
2009 particle_element3i[i*6+0] = i*4+0;
2010 particle_element3i[i*6+1] = i*4+1;
2011 particle_element3i[i*6+2] = i*4+2;
2012 particle_element3i[i*6+3] = i*4+0;
2013 particle_element3i[i*6+4] = i*4+2;
2014 particle_element3i[i*6+5] = i*4+3;
2017 Cvar_RegisterVariable(&r_drawparticles);
2018 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2021 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2023 int surfacelistindex;
2024 int batchstart, batchcount;
2025 const particle_t *p;
2027 rtexture_t *texture;
2028 float *v3f, *t2f, *c4f;
2030 R_Mesh_Matrix(&identitymatrix);
2031 R_Mesh_ResetTextureState();
2032 R_Mesh_VertexPointer(particle_vertex3f);
2033 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f);
2034 R_Mesh_ColorPointer(particle_color4f);
2035 GL_DepthMask(false);
2037 GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
2039 // first generate all the vertices at once
2040 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2042 particletexture_t *tex;
2044 float up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
2046 p = cl.particles + surfacelist[surfacelistindex];
2048 blendmode = p->type->blendmode;
2050 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2051 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2052 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2053 ca = p->alpha * (1.0f / 255.0f);
2054 if (blendmode == PBLEND_MOD)
2064 ca /= cl_particles_quality.value;
2065 if (p->type->lighting)
2067 float ambient[3], diffuse[3], diffusenormal[3];
2068 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2069 cr *= (ambient[0] + 0.5 * diffuse[0]);
2070 cg *= (ambient[1] + 0.5 * diffuse[1]);
2071 cb *= (ambient[2] + 0.5 * diffuse[2]);
2073 if (r_refdef.fogenabled)
2075 fog = VERTEXFOGTABLE(VectorDistance(p->org, r_view.origin));
2080 if (blendmode == PBLEND_ALPHA)
2082 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2083 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2084 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2087 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2088 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2089 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2090 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2092 size = p->size * cl_particles_size.value;
2094 tex = &particletexture[p->texnum];
2095 if (p->type->orientation == PARTICLE_BILLBOARD)
2097 VectorScale(r_view.left, -size, right);
2098 VectorScale(r_view.up, size, up);
2099 v3f[ 0] = org[0] - right[0] - up[0];
2100 v3f[ 1] = org[1] - right[1] - up[1];
2101 v3f[ 2] = org[2] - right[2] - up[2];
2102 v3f[ 3] = org[0] - right[0] + up[0];
2103 v3f[ 4] = org[1] - right[1] + up[1];
2104 v3f[ 5] = org[2] - right[2] + up[2];
2105 v3f[ 6] = org[0] + right[0] + up[0];
2106 v3f[ 7] = org[1] + right[1] + up[1];
2107 v3f[ 8] = org[2] + right[2] + up[2];
2108 v3f[ 9] = org[0] + right[0] - up[0];
2109 v3f[10] = org[1] + right[1] - up[1];
2110 v3f[11] = org[2] + right[2] - up[2];
2111 t2f[0] = tex->s1;t2f[1] = tex->t2;
2112 t2f[2] = tex->s1;t2f[3] = tex->t1;
2113 t2f[4] = tex->s2;t2f[5] = tex->t1;
2114 t2f[6] = tex->s2;t2f[7] = tex->t2;
2116 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2119 if (DotProduct(p->vel, r_view.origin) > DotProduct(p->vel, org))
2121 VectorNegate(p->vel, v);
2122 VectorVectors(v, right, up);
2125 VectorVectors(p->vel, right, up);
2126 VectorScale(right, size, right);
2127 VectorScale(up, size, up);
2128 v3f[ 0] = org[0] - right[0] - up[0];
2129 v3f[ 1] = org[1] - right[1] - up[1];
2130 v3f[ 2] = org[2] - right[2] - up[2];
2131 v3f[ 3] = org[0] - right[0] + up[0];
2132 v3f[ 4] = org[1] - right[1] + up[1];
2133 v3f[ 5] = org[2] - right[2] + up[2];
2134 v3f[ 6] = org[0] + right[0] + up[0];
2135 v3f[ 7] = org[1] + right[1] + up[1];
2136 v3f[ 8] = org[2] + right[2] + up[2];
2137 v3f[ 9] = org[0] + right[0] - up[0];
2138 v3f[10] = org[1] + right[1] - up[1];
2139 v3f[11] = org[2] + right[2] - up[2];
2140 t2f[0] = tex->s1;t2f[1] = tex->t2;
2141 t2f[2] = tex->s1;t2f[3] = tex->t1;
2142 t2f[4] = tex->s2;t2f[5] = tex->t1;
2143 t2f[6] = tex->s2;t2f[7] = tex->t2;
2145 else if (p->type->orientation == PARTICLE_SPARK)
2147 VectorMA(org, -0.02, p->vel, v);
2148 VectorMA(org, 0.02, p->vel, up2);
2149 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2150 t2f[0] = tex->s1;t2f[1] = tex->t2;
2151 t2f[2] = tex->s1;t2f[3] = tex->t1;
2152 t2f[4] = tex->s2;t2f[5] = tex->t1;
2153 t2f[6] = tex->s2;t2f[7] = tex->t2;
2155 else if (p->type->orientation == PARTICLE_BEAM)
2157 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2158 VectorSubtract(p->vel, org, up);
2159 VectorNormalize(up);
2160 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2161 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2162 t2f[0] = 1;t2f[1] = v[0];
2163 t2f[2] = 0;t2f[3] = v[0];
2164 t2f[4] = 0;t2f[5] = v[1];
2165 t2f[6] = 1;t2f[7] = v[1];
2169 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2174 // now render batches of particles based on blendmode and texture
2175 blendmode = PBLEND_ADD;
2176 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2177 texture = particletexture[63].texture;
2178 R_Mesh_TexBind(0, R_GetTexture(texture));
2179 GL_LockArrays(0, numsurfaces*4);
2182 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2184 p = cl.particles + surfacelist[surfacelistindex];
2186 if (blendmode != p->type->blendmode)
2189 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2191 batchstart = surfacelistindex;
2192 blendmode = p->type->blendmode;
2193 if (blendmode == PBLEND_ALPHA)
2194 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2195 else if (blendmode == PBLEND_ADD)
2196 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2197 else //if (blendmode == PBLEND_MOD)
2198 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2200 if (texture != particletexture[p->texnum].texture)
2203 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2205 batchstart = surfacelistindex;
2206 texture = particletexture[p->texnum].texture;
2207 R_Mesh_TexBind(0, R_GetTexture(texture));
2213 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2214 GL_LockArrays(0, 0);
2217 void R_DrawParticles (void)
2220 float minparticledist;
2223 // LordHavoc: early out conditions
2224 if ((!cl.num_particles) || (!r_drawparticles.integer))
2227 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2229 // LordHavoc: only render if not too close
2230 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2234 r_refdef.stats.particles++;
2235 if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2236 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);