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] =
372 "TE_TEI_BIGEXPLOSION",
388 void CL_Particles_LoadEffectInfo(void)
391 unsigned char *filedata;
392 fs_offset_t filesize;
393 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
394 memset(particleeffectname, 0, sizeof(particleeffectname));
395 for (i = 0;i < EFFECT_TOTAL;i++)
396 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
397 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
400 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
410 void CL_ReadPointFile_f (void);
411 void CL_Particles_Init (void)
413 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)");
414 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
416 Cvar_RegisterVariable (&cl_particles);
417 Cvar_RegisterVariable (&cl_particles_quality);
418 Cvar_RegisterVariable (&cl_particles_size);
419 Cvar_RegisterVariable (&cl_particles_quake);
420 Cvar_RegisterVariable (&cl_particles_blood);
421 Cvar_RegisterVariable (&cl_particles_blood_alpha);
422 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
423 Cvar_RegisterVariable (&cl_particles_explosions_smoke);
424 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
425 Cvar_RegisterVariable (&cl_particles_explosions_shell);
426 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
427 Cvar_RegisterVariable (&cl_particles_smoke);
428 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
429 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
430 Cvar_RegisterVariable (&cl_particles_sparks);
431 Cvar_RegisterVariable (&cl_particles_bubbles);
432 Cvar_RegisterVariable (&cl_decals);
433 Cvar_RegisterVariable (&cl_decals_time);
434 Cvar_RegisterVariable (&cl_decals_fadetime);
437 void CL_Particles_Shutdown (void)
441 // list of all 26 parameters:
442 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
443 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
444 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
445 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
446 // palpha - opacity of particle as 0-255 (can be more than 255)
447 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
448 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
449 // pgravity - how much effect gravity has on the particle (0-1)
450 // 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
451 // px,py,pz - starting origin of particle
452 // pvx,pvy,pvz - starting velocity of particle
453 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
454 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 pfriction, float originjitter, float velocityjitter)
459 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].type;cl.free_particle++);
460 if (cl.free_particle >= cl.max_particles)
462 part = &cl.particles[cl.free_particle++];
463 if (cl.num_particles < cl.free_particle)
464 cl.num_particles = cl.free_particle;
465 memset(part, 0, sizeof(*part));
467 l2 = (int)lhrandom(0.5, 256.5);
469 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
470 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
471 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
472 part->color[3] = 0xFF;
475 part->sizeincrease = psizeincrease;
476 part->alpha = palpha;
477 part->alphafade = palphafade;
478 part->gravity = pgravity;
479 part->bounce = pbounce;
481 part->org[0] = px + originjitter * v[0];
482 part->org[1] = py + originjitter * v[1];
483 part->org[2] = pz + originjitter * v[2];
484 part->vel[0] = pvx + velocityjitter * v[0];
485 part->vel[1] = pvy + velocityjitter * v[1];
486 part->vel[2] = pvz + velocityjitter * v[2];
488 // FIXME: change parameters to have separate air and liquid friction
489 // (as supported by effectinfo.txt)
490 part->airfriction = pfriction;
491 part->liquidfriction = pfriction * 4;
495 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
498 if (!cl_decals.integer)
500 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);
505 p->ownermodel = cl.entities[p->owner].render.model;
506 VectorAdd(org, normal, p->org);
507 VectorCopy(normal, p->vel);
508 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
509 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
510 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
514 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
517 float bestfrac, bestorg[3], bestnormal[3];
519 int besthitent = 0, hitent;
522 for (i = 0;i < 32;i++)
525 VectorMA(org, maxdist, org2, org2);
526 trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, false);
527 // take the closest trace result that doesn't end up hitting a NOMARKS
528 // surface (sky for example)
529 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
531 bestfrac = trace.fraction;
533 VectorCopy(trace.endpos, bestorg);
534 VectorCopy(trace.plane.normal, bestnormal);
538 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
541 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount);
542 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)
545 matrix4x4_t tempmatrix;
546 VectorLerp(originmins, 0.5, originmaxs, center);
547 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
548 if (effectnameindex == EFFECT_SVC_PARTICLE)
550 if (cl_particles.integer)
552 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
554 CL_ParticleExplosion(center);
555 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
556 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
559 count *= cl_particles_quality.value;
560 for (;count > 0;count--)
562 int k = particlepalette[palettecolor + (rand()&7)];
563 if (cl_particles_quake.integer)
564 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, 8, 0);
565 else if (gamemode == GAME_GOODVSBAD2)
566 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, 8, 10);
568 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, 8, 15);
573 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
574 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
575 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
576 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
577 else if (effectnameindex == EFFECT_TE_SPIKE)
579 if (cl_particles_bulletimpacts.integer)
581 if (cl_particles_quake.integer)
583 if (cl_particles_smoke.integer)
584 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
587 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
590 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
591 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
593 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
595 if (cl_particles_bulletimpacts.integer)
597 if (cl_particles_quake.integer)
599 if (cl_particles_smoke.integer)
600 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
603 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
606 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
607 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
608 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);
610 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
612 if (cl_particles_bulletimpacts.integer)
614 if (cl_particles_quake.integer)
616 if (cl_particles_smoke.integer)
617 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
620 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
623 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
624 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
626 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
628 if (cl_particles_bulletimpacts.integer)
630 if (cl_particles_quake.integer)
632 if (cl_particles_smoke.integer)
633 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
636 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
639 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
640 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
641 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);
643 else if (effectnameindex == EFFECT_TE_BLOOD)
645 if (!cl_particles_blood.integer)
647 if (cl_particles_quake.integer)
648 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
651 static double bloodaccumulator = 0;
652 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
653 for (;bloodaccumulator > 0;bloodaccumulator--)
654 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, 0, 64);
657 else if (effectnameindex == EFFECT_TE_SPARK)
658 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count, 0);
659 else if (effectnameindex == EFFECT_TE_PLASMABURN)
661 // plasma scorch mark
662 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
663 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
664 CL_AllocDlight(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
666 else if (effectnameindex == EFFECT_TE_GUNSHOT)
668 if (cl_particles_bulletimpacts.integer)
670 if (cl_particles_quake.integer)
671 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
673 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
676 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
677 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
679 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
681 if (cl_particles_bulletimpacts.integer)
683 if (cl_particles_quake.integer)
684 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
686 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
689 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
690 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
691 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);
693 else if (effectnameindex == EFFECT_TE_EXPLOSION)
695 CL_ParticleExplosion(center);
696 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);
698 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
700 CL_ParticleExplosion(center);
701 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);
703 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
705 if (cl_particles_quake.integer)
708 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
711 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, 16, 256);
713 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, 16, 0);
717 CL_ParticleExplosion(center);
718 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);
720 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
721 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);
722 else if (effectnameindex == EFFECT_TE_FLAMEJET)
724 count *= cl_particles_quality.value;
726 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, 0, 128);
728 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
730 float i, j, inc, vel;
733 inc = 8 / cl_particles_quality.value;
734 for (i = -128;i < 128;i += inc)
736 for (j = -128;j < 128;j += inc)
738 dir[0] = j + lhrandom(0, inc);
739 dir[1] = i + lhrandom(0, inc);
741 org[0] = center[0] + dir[0];
742 org[1] = center[1] + dir[1];
743 org[2] = center[2] + lhrandom(0, 64);
744 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
745 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);
749 else if (effectnameindex == EFFECT_TE_TELEPORT)
751 float i, j, k, inc, vel;
754 inc = 4 / cl_particles_quality.value;
755 for (i = -16;i < 16;i += inc)
757 for (j = -16;j < 16;j += inc)
759 for (k = -24;k < 32;k += inc)
761 VectorSet(dir, i*8, j*8, k*8);
762 VectorNormalize(dir);
763 vel = lhrandom(50, 113);
764 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);
768 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);
770 else if (effectnameindex == EFFECT_TE_TEI_G3)
771 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);
772 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
774 if (cl_particles_smoke.integer)
776 count *= 0.25f * cl_particles_quality.value;
778 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, 1.5f, 6.0f);
781 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
783 CL_ParticleExplosion(center);
784 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);
786 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
789 if (cl_stainmaps.integer)
790 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
791 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
792 if (cl_particles_smoke.integer)
793 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
794 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, 20, 155);
795 if (cl_particles_sparks.integer)
796 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
797 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, 465);
798 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);
800 else if (effectnameindex == EFFECT_EF_FLAME)
802 count *= 300 * cl_particles_quality.value;
804 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, 16, 128);
805 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);
807 else if (effectnameindex == EFFECT_EF_STARDUST)
809 count *= 200 * cl_particles_quality.value;
811 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, 16, 128);
812 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);
814 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
818 int smoke, blood, bubbles, r, color;
820 if (effectnameindex == EFFECT_TR_ROCKET)
821 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);
822 else if (effectnameindex == EFFECT_TR_VORESPIKE)
824 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
825 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);
827 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);
829 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
830 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);
832 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
835 VectorSubtract(originmaxs, originmins, dir);
836 len = VectorNormalizeLength(dir);
837 dec = -ent->persistent.trail_time;
838 ent->persistent.trail_time += len;
839 if (ent->persistent.trail_time < 0.01f)
842 // if we skip out, leave it reset
843 ent->persistent.trail_time = 0.0f;
845 // advance into this frame to reach the first puff location
846 VectorMA(originmins, dec, dir, pos);
849 smoke = cl_particles.integer && cl_particles_smoke.integer;
850 blood = cl_particles.integer && cl_particles_blood.integer;
851 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
852 qd = 1.0f / cl_particles_quality.value;
859 if (effectnameindex == EFFECT_TR_BLOOD)
861 if (cl_particles_quake.integer)
863 color = particlepalette[67 + (rand()&3)];
864 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, 3, 0);
869 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, 0, 64);
872 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
874 if (cl_particles_quake.integer)
877 color = particlepalette[67 + (rand()&3)];
878 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, 3, 0);
883 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, 0, 64);
889 if (effectnameindex == EFFECT_TR_ROCKET)
891 if (cl_particles_quake.integer)
894 color = particlepalette[ramp3[r]];
895 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, 3, 0);
899 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);
900 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, 20);
903 else if (effectnameindex == EFFECT_TR_GRENADE)
905 if (cl_particles_quake.integer)
908 color = particlepalette[ramp3[r]];
909 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, 3, 0);
913 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);
916 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
918 if (cl_particles_quake.integer)
921 color = particlepalette[52 + (rand()&7)];
922 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);
923 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);
925 else if (gamemode == GAME_GOODVSBAD2)
928 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);
932 color = particlepalette[20 + (rand()&7)];
933 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);
936 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
938 if (cl_particles_quake.integer)
941 color = particlepalette[230 + (rand()&7)];
942 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);
943 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);
947 color = particlepalette[226 + (rand()&7)];
948 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);
951 else if (effectnameindex == EFFECT_TR_VORESPIKE)
953 if (cl_particles_quake.integer)
955 color = particlepalette[152 + (rand()&3)];
956 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 8, 0);
958 else if (gamemode == GAME_GOODVSBAD2)
961 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);
963 else if (gamemode == GAME_PRYDON)
966 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);
969 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);
971 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
974 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, 4);
976 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
979 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, 16);
981 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
982 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);
986 if (effectnameindex == EFFECT_TR_ROCKET)
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, (1.0 / 16.0), 0, 16);
988 else if (effectnameindex == EFFECT_TR_GRENADE)
989 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, (1.0 / 16.0), 0, 16);
991 // advance to next time and position
994 VectorMA (pos, dec, dir, pos);
996 ent->persistent.trail_time = len;
998 else if (developer.integer >= 1)
999 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1002 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)
1005 qboolean found = false;
1006 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1007 return; // invalid effect index
1008 if (!particleeffectname[effectnameindex][0])
1009 return; // no such effect
1010 VectorLerp(originmins, 0.5, originmaxs, center);
1011 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1013 int effectinfoindex;
1016 particleeffectinfo_t *info;
1018 vec3_t centervelocity;
1024 qboolean underwater;
1025 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1026 VectorLerp(originmins, 0.5, originmaxs, center);
1027 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1028 supercontents = CL_PointSuperContents(center);
1029 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1030 VectorSubtract(originmaxs, originmins, traildir);
1031 traillen = VectorLength(traildir);
1032 VectorNormalize(traildir);
1033 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1035 if (info->effectnameindex == effectnameindex)
1038 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1040 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1043 // spawn a dlight if requested
1044 if (info->lightradiusstart > 0)
1046 matrix4x4_t tempmatrix;
1047 if (info->trailspacing > 0)
1048 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1050 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1051 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);
1056 if (info->tex[1] > info->tex[0])
1058 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1059 tex = min(tex, info->tex[1] - 1);
1061 if (info->particletype == pt_decal)
1062 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]);
1063 else if (info->particletype == pt_beam)
1064 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);
1067 if (!cl_particles.integer)
1069 switch (info->particletype)
1071 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1072 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1073 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1074 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1077 VectorCopy(originmins, trailpos);
1078 if (info->trailspacing > 0)
1080 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1081 trailstep = info->trailspacing / cl_particles_quality.value;
1085 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1088 for (;info->particleaccumulator > 0;info->particleaccumulator--)
1090 if (info->tex[1] > info->tex[0])
1092 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1093 tex = min(tex, info->tex[1] - 1);
1097 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1098 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1099 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1102 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, 0, 0);
1104 VectorMA(trailpos, trailstep, traildir, trailpos);
1111 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor);
1119 void CL_EntityParticles (const entity_t *ent)
1122 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1123 static vec3_t avelocities[NUMVERTEXNORMALS];
1124 if (!cl_particles.integer) return;
1126 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1128 if (!avelocities[0][0])
1129 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1130 avelocities[0][i] = lhrandom(0, 2.55);
1132 for (i = 0;i < NUMVERTEXNORMALS;i++)
1134 yaw = cl.time * avelocities[i][0];
1135 pitch = cl.time * avelocities[i][1];
1136 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1137 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1138 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1139 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);
1144 void CL_ReadPointFile_f (void)
1146 vec3_t org, leakorg;
1148 char *pointfile = NULL, *pointfilepos, *t, tchar;
1149 char name[MAX_OSPATH];
1154 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1155 strlcat (name, ".pts", sizeof (name));
1156 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1159 Con_Printf("Could not open %s\n", name);
1163 Con_Printf("Reading %s...\n", name);
1164 VectorClear(leakorg);
1167 pointfilepos = pointfile;
1168 while (*pointfilepos)
1170 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1175 while (*t && *t != '\n' && *t != '\r')
1179 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1185 VectorCopy(org, leakorg);
1188 if (cl.num_particles < cl.max_particles - 3)
1191 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);
1194 Mem_Free(pointfile);
1195 VectorCopy(leakorg, org);
1196 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1198 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);
1199 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);
1200 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);
1205 CL_ParseParticleEffect
1207 Parse an effect out of the server message
1210 void CL_ParseParticleEffect (void)
1213 int i, count, msgcount, color;
1215 MSG_ReadVector(org, cls.protocol);
1216 for (i=0 ; i<3 ; i++)
1217 dir[i] = MSG_ReadChar ();
1218 msgcount = MSG_ReadByte ();
1219 color = MSG_ReadByte ();
1221 if (msgcount == 255)
1226 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1231 CL_ParticleExplosion
1235 void CL_ParticleExplosion (const vec3_t org)
1241 if (cl_stainmaps.integer)
1242 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1243 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1245 if (cl_particles_quake.integer)
1247 for (i = 0;i < 1024;i++)
1253 color = particlepalette[ramp1[r]];
1254 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, 16, 256);
1258 color = particlepalette[ramp2[r]];
1259 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, 16, 256);
1265 i = CL_PointSuperContents(org);
1266 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1268 if (cl_particles.integer && cl_particles_bubbles.integer)
1269 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1270 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, (1.0 / 16.0), 16, 96);
1274 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1276 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1278 for (i = 0;i < 32;i++)
1282 for (k = 0;k < 16;k++)
1284 v[0] = org[0] + lhrandom(-48, 48);
1285 v[1] = org[1] + lhrandom(-48, 48);
1286 v[2] = org[2] + lhrandom(-48, 48);
1287 trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
1288 if (trace.fraction >= 0.1)
1291 VectorSubtract(trace.endpos, org, v2);
1292 VectorScale(v2, 2.0f, v2);
1293 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);
1297 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1298 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1299 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, 256);
1303 if (cl_particles_explosions_shell.integer)
1304 R_NewExplosion(org);
1309 CL_ParticleExplosion2
1313 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1316 if (!cl_particles.integer) return;
1318 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1320 k = particlepalette[colorStart + (i % colorLength)];
1321 if (cl_particles_quake.integer)
1322 particle(particletype + pt_static, k, k, tex_particle, 1, 0, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, 8, 256);
1324 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), 8, 192);
1328 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1330 if (cl_particles_sparks.integer)
1332 sparkcount *= cl_particles_quality.value;
1333 while(sparkcount-- > 0)
1334 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, 64);
1336 if (cl_particles_smoke.integer)
1338 smokecount *= cl_particles_quality.value;
1339 while(smokecount-- > 0)
1340 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, 8);
1344 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)
1347 if (!cl_particles.integer) return;
1349 count = (int)(count * cl_particles_quality.value);
1352 k = particlepalette[colorbase + (rand()&3)];
1353 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, randomvel);
1357 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1360 float z, minz, maxz;
1362 if (!cl_particles.integer) return;
1363 if (dir[2] < 0) // falling
1368 minz = z - fabs(dir[2]) * 0.1;
1369 maxz = z + fabs(dir[2]) * 0.1;
1370 minz = bound(mins[2], minz, maxs[2]);
1371 maxz = bound(mins[2], maxz, maxs[2]);
1373 count = (int)(count * cl_particles_quality.value);
1378 count *= 4; // ick, this should be in the mod or maps?
1382 k = particlepalette[colorbase + (rand()&3)];
1383 if (gamemode == GAME_GOODVSBAD2)
1384 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);
1386 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);
1392 k = particlepalette[colorbase + (rand()&3)];
1393 if (gamemode == GAME_GOODVSBAD2)
1394 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);
1396 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);
1398 VectorCopy(p->vel, p->relativedirection);
1402 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1411 void CL_MoveParticles (void)
1414 int i, maxparticle, j, a, content;
1415 float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
1416 particletype_t *decaltype, *bloodtype;
1420 // LordHavoc: early out condition
1421 if (!cl.num_particles)
1423 cl.free_particle = 0;
1427 frametime = cl.time - cl.oldtime;
1428 gravity = frametime * sv_gravity.value;
1429 dvel = 1+4*frametime;
1430 decalfade = frametime * 255 / cl_decals_fadetime.value;
1431 decaltype = particletype + pt_decal;
1432 bloodtype = particletype + pt_blood;
1436 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1440 if (cl.free_particle > i)
1441 cl.free_particle = i;
1446 // heavily optimized decal case
1447 if (p->type == decaltype)
1449 // FIXME: this has fairly wacky handling of alpha
1450 if (cl.time > p->time2 + cl_decals_time.value)
1452 p->alpha -= decalfade;
1456 if (cl.free_particle > i)
1457 cl.free_particle = i;
1463 if (cl.entities[p->owner].render.model == p->ownermodel)
1465 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1466 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1471 if (cl.free_particle > i)
1472 cl.free_particle = i;
1480 p->alpha -= p->alphafade * frametime;
1485 if (cl.free_particle > i)
1486 cl.free_particle = i;
1490 if (p->type->orientation != PARTICLE_BEAM)
1492 VectorCopy(p->org, oldorg);
1493 VectorMA(p->org, frametime, p->vel, p->org);
1494 VectorCopy(p->org, org);
1497 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);
1498 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1499 // or if the trace hit something flagged as NOIMPACT
1500 // then remove the particle
1501 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1506 // react if the particle hit something
1507 if (trace.fraction < 1)
1509 VectorCopy(trace.endpos, p->org);
1510 if (p->type == particletype + pt_rain)
1512 // raindrop - splash on solid/water/slime/lava
1514 // convert from a raindrop particle to a rainsplash decal
1515 VectorCopy(trace.plane.normal, p->vel);
1516 VectorAdd(p->org, p->vel, p->org);
1517 p->type = particletype + pt_raindecal;
1518 p->texnum = tex_rainsplash;
1520 p->alphafade = p->alpha / 0.4;
1523 p->liquidfriction = 0;
1526 p->sizeincrease = p->size * 16;
1529 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, 32);
1531 else if (p->type == bloodtype)
1533 // blood - splash on solid
1534 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1539 if (!cl_decals.integer)
1544 // convert from a blood particle to a blood decal
1545 VectorCopy(trace.plane.normal, p->vel);
1546 VectorAdd(p->org, p->vel, p->org);
1547 if (cl_stainmaps.integer)
1548 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)));
1550 p->type = particletype + pt_decal;
1551 p->texnum = tex_blooddecal[rand()&7];
1553 p->ownermodel = cl.entities[hitent].render.model;
1554 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1555 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1556 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1561 p->liquidfriction = 0;
1565 else if (p->bounce < 0)
1567 // bounce -1 means remove on impact
1573 // anything else - bounce off solid
1574 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1575 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1576 if (DotProduct(p->vel, p->vel) < 0.03)
1577 VectorClear(p->vel);
1581 p->vel[2] -= p->gravity * gravity;
1583 if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1585 f = 1.0f - min(p->liquidfriction * frametime, 1);
1586 VectorScale(p->vel, f, p->vel);
1588 else if (p->airfriction)
1590 f = 1.0f - min(p->airfriction * frametime, 1);
1591 VectorScale(p->vel, f, p->vel);
1595 if (p->type != particletype + pt_static)
1597 switch (p->type - particletype)
1599 case pt_entityparticle:
1600 // particle that removes itself after one rendered frame
1607 a = CL_PointSuperContents(p->org);
1608 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1610 p->size += frametime * 8;
1611 //p->alpha -= bloodwaterfade;
1614 p->vel[2] -= gravity;
1615 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1619 a = CL_PointSuperContents(p->org);
1620 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1627 a = CL_PointSuperContents(p->org);
1628 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1632 if (cl.time > p->time2)
1635 p->time2 = cl.time + (rand() & 3) * 0.1;
1636 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1637 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1638 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1640 a = CL_PointSuperContents(p->org);
1641 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1649 cl.num_particles = maxparticle + 1;
1652 #define MAX_PARTICLETEXTURES 64
1653 // particletexture_t is a rectangle in the particlefonttexture
1654 typedef struct particletexture_s
1656 rtexture_t *texture;
1657 float s1, t1, s2, t2;
1661 static rtexturepool_t *particletexturepool;
1662 static rtexture_t *particlefonttexture;
1663 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1665 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1667 #define PARTICLETEXTURESIZE 64
1668 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1670 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1674 dz = 1 - (dx*dx+dy*dy);
1675 if (dz > 0) // it does hit the sphere
1679 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1680 VectorNormalize(normal);
1681 dot = DotProduct(normal, light);
1682 if (dot > 0.5) // interior reflection
1683 f += ((dot * 2) - 1);
1684 else if (dot < -0.5) // exterior reflection
1685 f += ((dot * -2) - 1);
1687 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1688 VectorNormalize(normal);
1689 dot = DotProduct(normal, light);
1690 if (dot > 0.5) // interior reflection
1691 f += ((dot * 2) - 1);
1692 else if (dot < -0.5) // exterior reflection
1693 f += ((dot * -2) - 1);
1695 f += 16; // just to give it a haze so you can see the outline
1696 f = bound(0, f, 255);
1697 return (unsigned char) f;
1703 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1705 int basex, basey, y;
1706 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1707 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1708 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1709 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1712 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1715 float cx, cy, dx, dy, f, iradius;
1717 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1718 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1719 iradius = 1.0f / radius;
1720 alpha *= (1.0f / 255.0f);
1721 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1723 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1727 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1730 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1731 d[0] += (int)(f * (red - d[0]));
1732 d[1] += (int)(f * (green - d[1]));
1733 d[2] += (int)(f * (blue - d[2]));
1739 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1742 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1744 data[0] = bound(minr, data[0], maxr);
1745 data[1] = bound(ming, data[1], maxg);
1746 data[2] = bound(minb, data[2], maxb);
1750 void particletextureinvert(unsigned char *data)
1753 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1755 data[0] = 255 - data[0];
1756 data[1] = 255 - data[1];
1757 data[2] = 255 - data[2];
1761 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1762 static void R_InitBloodTextures (unsigned char *particletexturedata)
1765 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1768 for (i = 0;i < 8;i++)
1770 memset(&data[0][0][0], 255, sizeof(data));
1771 for (k = 0;k < 24;k++)
1772 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1773 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1774 particletextureinvert(&data[0][0][0]);
1775 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1779 for (i = 0;i < 8;i++)
1781 memset(&data[0][0][0], 255, sizeof(data));
1783 for (j = 1;j < 10;j++)
1784 for (k = min(j, m - 1);k < m;k++)
1785 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1786 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1787 particletextureinvert(&data[0][0][0]);
1788 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1793 //uncomment this to make engine save out particle font to a tga file when run
1794 //#define DUMPPARTICLEFONT
1796 static void R_InitParticleTexture (void)
1798 int x, y, d, i, k, m;
1802 // a note: decals need to modulate (multiply) the background color to
1803 // properly darken it (stain), and they need to be able to alpha fade,
1804 // this is a very difficult challenge because it means fading to white
1805 // (no change to background) rather than black (darkening everything
1806 // behind the whole decal polygon), and to accomplish this the texture is
1807 // inverted (dark red blood on white background becomes brilliant cyan
1808 // and white on black background) so we can alpha fade it to black, then
1809 // we invert it again during the blendfunc to make it work...
1811 #ifndef DUMPPARTICLEFONT
1812 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1813 if (!particlefonttexture)
1816 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1817 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1818 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1821 for (i = 0;i < 8;i++)
1823 memset(&data[0][0][0], 255, sizeof(data));
1826 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1828 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1829 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1831 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1833 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1834 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1836 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1837 d = (noise2[y][x] - 128) * 3 + 192;
1839 d = (int)(d * (1-(dx*dx+dy*dy)));
1840 d = (d * noise1[y][x]) >> 7;
1841 d = bound(0, d, 255);
1842 data[y][x][3] = (unsigned char) d;
1849 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1853 memset(&data[0][0][0], 255, sizeof(data));
1854 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1856 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1857 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1859 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1860 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1861 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1864 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1867 memset(&data[0][0][0], 255, sizeof(data));
1868 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1870 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1871 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1873 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1874 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1875 d = bound(0, d, 255);
1876 data[y][x][3] = (unsigned char) d;
1879 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1882 memset(&data[0][0][0], 255, sizeof(data));
1883 light[0] = 1;light[1] = 1;light[2] = 1;
1884 VectorNormalize(light);
1885 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1887 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1888 // stretch upper half of bubble by +50% and shrink lower half by -50%
1889 // (this gives an elongated teardrop shape)
1891 dy = (dy - 0.5f) * 2.0f;
1893 dy = (dy - 0.5f) / 1.5f;
1894 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1896 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1897 // shrink bubble width to half
1899 data[y][x][3] = shadebubble(dx, dy, light);
1902 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1905 memset(&data[0][0][0], 255, sizeof(data));
1906 light[0] = 1;light[1] = 1;light[2] = 1;
1907 VectorNormalize(light);
1908 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1910 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1911 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1913 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1914 data[y][x][3] = shadebubble(dx, dy, light);
1917 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1919 // Blood particles and blood decals
1920 R_InitBloodTextures (particletexturedata);
1923 for (i = 0;i < 8;i++)
1925 memset(&data[0][0][0], 255, sizeof(data));
1926 for (k = 0;k < 12;k++)
1927 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1928 for (k = 0;k < 3;k++)
1929 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1930 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1931 particletextureinvert(&data[0][0][0]);
1932 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1935 #ifdef DUMPPARTICLEFONT
1936 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1939 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1941 Mem_Free(particletexturedata);
1943 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1945 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1946 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1947 particletexture[i].texture = particlefonttexture;
1948 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1949 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1950 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1951 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1954 #ifndef DUMPPARTICLEFONT
1955 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1956 if (!particletexture[tex_beam].texture)
1959 unsigned char noise3[64][64], data2[64][16][4];
1961 fractalnoise(&noise3[0][0], 64, 4);
1963 for (y = 0;y < 64;y++)
1965 dy = (y - 0.5f*64) / (64*0.5f-1);
1966 for (x = 0;x < 16;x++)
1968 dx = (x - 0.5f*16) / (16*0.5f-2);
1969 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1970 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1971 data2[y][x][3] = 255;
1975 #ifdef DUMPPARTICLEFONT
1976 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1978 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1980 particletexture[tex_beam].s1 = 0;
1981 particletexture[tex_beam].t1 = 0;
1982 particletexture[tex_beam].s2 = 1;
1983 particletexture[tex_beam].t2 = 1;
1986 static void r_part_start(void)
1988 particletexturepool = R_AllocTexturePool();
1989 R_InitParticleTexture ();
1990 CL_Particles_LoadEffectInfo();
1993 static void r_part_shutdown(void)
1995 R_FreeTexturePool(&particletexturepool);
1998 static void r_part_newmap(void)
2002 #define BATCHSIZE 256
2003 int particle_element3i[BATCHSIZE*6];
2004 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2006 void R_Particles_Init (void)
2009 for (i = 0;i < BATCHSIZE;i++)
2011 particle_element3i[i*6+0] = i*4+0;
2012 particle_element3i[i*6+1] = i*4+1;
2013 particle_element3i[i*6+2] = i*4+2;
2014 particle_element3i[i*6+3] = i*4+0;
2015 particle_element3i[i*6+4] = i*4+2;
2016 particle_element3i[i*6+5] = i*4+3;
2019 Cvar_RegisterVariable(&r_drawparticles);
2020 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2023 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2025 int surfacelistindex;
2026 int batchstart, batchcount;
2027 const particle_t *p;
2029 rtexture_t *texture;
2030 float *v3f, *t2f, *c4f;
2032 R_Mesh_Matrix(&identitymatrix);
2033 R_Mesh_ResetTextureState();
2034 R_Mesh_VertexPointer(particle_vertex3f);
2035 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f);
2036 R_Mesh_ColorPointer(particle_color4f);
2037 GL_DepthMask(false);
2040 // first generate all the vertices at once
2041 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2043 particletexture_t *tex;
2045 float up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
2047 p = cl.particles + surfacelist[surfacelistindex];
2049 blendmode = p->type->blendmode;
2051 cr = p->color[0] * (1.0f / 255.0f);
2052 cg = p->color[1] * (1.0f / 255.0f);
2053 cb = p->color[2] * (1.0f / 255.0f);
2054 ca = p->alpha * (1.0f / 255.0f);
2055 if (blendmode == PBLEND_MOD)
2065 ca /= cl_particles_quality.value;
2066 if (p->type->lighting)
2068 float ambient[3], diffuse[3], diffusenormal[3];
2069 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2070 cr *= (ambient[0] + 0.5 * diffuse[0]);
2071 cg *= (ambient[1] + 0.5 * diffuse[1]);
2072 cb *= (ambient[2] + 0.5 * diffuse[2]);
2076 fog = VERTEXFOGTABLE(VectorDistance(p->org, r_vieworigin));
2081 if (blendmode == PBLEND_ALPHA)
2083 cr += fogcolor[0] * fog;
2084 cg += fogcolor[1] * fog;
2085 cb += fogcolor[2] * fog;
2088 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2089 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2090 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2091 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2093 size = p->size * cl_particles_size.value;
2095 tex = &particletexture[p->texnum];
2096 if (p->type->orientation == PARTICLE_BILLBOARD)
2098 VectorScale(r_viewleft, -size, right);
2099 VectorScale(r_viewup, size, up);
2100 v3f[ 0] = org[0] - right[0] - up[0];
2101 v3f[ 1] = org[1] - right[1] - up[1];
2102 v3f[ 2] = org[2] - right[2] - up[2];
2103 v3f[ 3] = org[0] - right[0] + up[0];
2104 v3f[ 4] = org[1] - right[1] + up[1];
2105 v3f[ 5] = org[2] - right[2] + up[2];
2106 v3f[ 6] = org[0] + right[0] + up[0];
2107 v3f[ 7] = org[1] + right[1] + up[1];
2108 v3f[ 8] = org[2] + right[2] + up[2];
2109 v3f[ 9] = org[0] + right[0] - up[0];
2110 v3f[10] = org[1] + right[1] - up[1];
2111 v3f[11] = org[2] + right[2] - up[2];
2112 t2f[0] = tex->s1;t2f[1] = tex->t2;
2113 t2f[2] = tex->s1;t2f[3] = tex->t1;
2114 t2f[4] = tex->s2;t2f[5] = tex->t1;
2115 t2f[6] = tex->s2;t2f[7] = tex->t2;
2117 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2120 if (DotProduct(p->vel, r_vieworigin) > DotProduct(p->vel, org))
2122 VectorNegate(p->vel, v);
2123 VectorVectors(v, right, up);
2126 VectorVectors(p->vel, right, up);
2127 VectorScale(right, size, right);
2128 VectorScale(up, size, up);
2129 v3f[ 0] = org[0] - right[0] - up[0];
2130 v3f[ 1] = org[1] - right[1] - up[1];
2131 v3f[ 2] = org[2] - right[2] - up[2];
2132 v3f[ 3] = org[0] - right[0] + up[0];
2133 v3f[ 4] = org[1] - right[1] + up[1];
2134 v3f[ 5] = org[2] - right[2] + up[2];
2135 v3f[ 6] = org[0] + right[0] + up[0];
2136 v3f[ 7] = org[1] + right[1] + up[1];
2137 v3f[ 8] = org[2] + right[2] + up[2];
2138 v3f[ 9] = org[0] + right[0] - up[0];
2139 v3f[10] = org[1] + right[1] - up[1];
2140 v3f[11] = org[2] + right[2] - up[2];
2141 t2f[0] = tex->s1;t2f[1] = tex->t2;
2142 t2f[2] = tex->s1;t2f[3] = tex->t1;
2143 t2f[4] = tex->s2;t2f[5] = tex->t1;
2144 t2f[6] = tex->s2;t2f[7] = tex->t2;
2146 else if (p->type->orientation == PARTICLE_SPARK)
2148 VectorMA(org, -0.02, p->vel, v);
2149 VectorMA(org, 0.02, p->vel, up2);
2150 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2151 t2f[0] = tex->s1;t2f[1] = tex->t2;
2152 t2f[2] = tex->s1;t2f[3] = tex->t1;
2153 t2f[4] = tex->s2;t2f[5] = tex->t1;
2154 t2f[6] = tex->s2;t2f[7] = tex->t2;
2156 else if (p->type->orientation == PARTICLE_BEAM)
2158 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2159 VectorSubtract(p->vel, org, up);
2160 VectorNormalize(up);
2161 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2162 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2163 t2f[0] = 1;t2f[1] = v[0];
2164 t2f[2] = 0;t2f[3] = v[0];
2165 t2f[4] = 0;t2f[5] = v[1];
2166 t2f[6] = 1;t2f[7] = v[1];
2170 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2175 // now render batches of particles based on blendmode and texture
2176 blendmode = PBLEND_ADD;
2177 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2178 texture = particletexture[63].texture;
2179 R_Mesh_TexBind(0, R_GetTexture(texture));
2180 GL_LockArrays(0, numsurfaces*4);
2183 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2185 p = cl.particles + surfacelist[surfacelistindex];
2187 if (blendmode != p->type->blendmode)
2190 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2192 batchstart = surfacelistindex;
2193 blendmode = p->type->blendmode;
2194 if (blendmode == PBLEND_ALPHA)
2195 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2196 else if (blendmode == PBLEND_ADD)
2197 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2198 else //if (blendmode == PBLEND_MOD)
2199 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2201 if (texture != particletexture[p->texnum].texture)
2204 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2206 batchstart = surfacelistindex;
2207 texture = particletexture[p->texnum].texture;
2208 R_Mesh_TexBind(0, R_GetTexture(texture));
2214 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2215 GL_LockArrays(0, 0);
2218 void R_DrawParticles (void)
2221 float minparticledist;
2224 // LordHavoc: early out conditions
2225 if ((!cl.num_particles) || (!r_drawparticles.integer))
2228 minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
2230 // LordHavoc: only render if not too close
2231 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2235 renderstats.particles++;
2236 if (DotProduct(p->org, r_viewforward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2237 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);