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 = 4 / 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 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);
767 else if (effectnameindex == EFFECT_TE_TEI_G3)
768 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);
769 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
771 if (cl_particles_smoke.integer)
773 count *= 0.25f * cl_particles_quality.value;
775 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);
778 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
780 CL_ParticleExplosion(center);
781 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);
783 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
786 if (cl_stainmaps.integer)
787 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
788 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
789 if (cl_particles_smoke.integer)
790 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
791 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);
792 if (cl_particles_sparks.integer)
793 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
794 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);
795 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);
797 else if (effectnameindex == EFFECT_EF_FLAME)
799 count *= 300 * cl_particles_quality.value;
801 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);
802 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);
804 else if (effectnameindex == EFFECT_EF_STARDUST)
806 count *= 200 * cl_particles_quality.value;
808 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);
809 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);
811 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
815 int smoke, blood, bubbles, r, color;
817 if (effectnameindex == EFFECT_TR_ROCKET)
818 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);
819 else if (effectnameindex == EFFECT_TR_VORESPIKE)
821 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
822 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);
824 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);
826 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
827 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);
829 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
832 VectorSubtract(originmaxs, originmins, dir);
833 len = VectorNormalizeLength(dir);
834 dec = -ent->persistent.trail_time;
835 ent->persistent.trail_time += len;
836 if (ent->persistent.trail_time < 0.01f)
839 // if we skip out, leave it reset
840 ent->persistent.trail_time = 0.0f;
842 // advance into this frame to reach the first puff location
843 VectorMA(originmins, dec, dir, pos);
846 smoke = cl_particles.integer && cl_particles_smoke.integer;
847 blood = cl_particles.integer && cl_particles_blood.integer;
848 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
849 qd = 1.0f / cl_particles_quality.value;
856 if (effectnameindex == EFFECT_TR_BLOOD)
858 if (cl_particles_quake.integer)
860 color = particlepalette[67 + (rand()&3)];
861 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);
866 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);
869 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
871 if (cl_particles_quake.integer)
874 color = particlepalette[67 + (rand()&3)];
875 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);
880 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);
886 if (effectnameindex == EFFECT_TR_ROCKET)
888 if (cl_particles_quake.integer)
891 color = particlepalette[ramp3[r]];
892 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);
896 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);
897 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);
900 else if (effectnameindex == EFFECT_TR_GRENADE)
902 if (cl_particles_quake.integer)
905 color = particlepalette[ramp3[r]];
906 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);
910 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);
913 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
915 if (cl_particles_quake.integer)
918 color = particlepalette[52 + (rand()&7)];
919 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);
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);
922 else if (gamemode == GAME_GOODVSBAD2)
925 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);
929 color = particlepalette[20 + (rand()&7)];
930 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);
933 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
935 if (cl_particles_quake.integer)
938 color = particlepalette[230 + (rand()&7)];
939 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);
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);
944 color = particlepalette[226 + (rand()&7)];
945 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);
948 else if (effectnameindex == EFFECT_TR_VORESPIKE)
950 if (cl_particles_quake.integer)
952 color = particlepalette[152 + (rand()&3)];
953 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);
955 else if (gamemode == GAME_GOODVSBAD2)
958 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);
960 else if (gamemode == GAME_PRYDON)
963 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);
966 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);
968 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
971 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);
973 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
976 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);
978 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
979 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);
983 if (effectnameindex == EFFECT_TR_ROCKET)
984 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);
985 else if (effectnameindex == EFFECT_TR_GRENADE)
986 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);
988 // advance to next time and position
991 VectorMA (pos, dec, dir, pos);
993 ent->persistent.trail_time = len;
995 else if (developer.integer >= 1)
996 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
999 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)
1002 qboolean found = false;
1003 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1004 return; // invalid effect index
1005 if (!particleeffectname[effectnameindex][0])
1006 return; // no such effect
1007 VectorLerp(originmins, 0.5, originmaxs, center);
1008 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1010 int effectinfoindex;
1013 particleeffectinfo_t *info;
1015 vec3_t centervelocity;
1021 qboolean underwater;
1022 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1023 VectorLerp(originmins, 0.5, originmaxs, center);
1024 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1025 supercontents = CL_PointSuperContents(center);
1026 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1027 VectorSubtract(originmaxs, originmins, traildir);
1028 traillen = VectorLength(traildir);
1029 VectorNormalize(traildir);
1030 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1032 if (info->effectnameindex == effectnameindex)
1035 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1037 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1040 // spawn a dlight if requested
1041 if (info->lightradiusstart > 0)
1043 matrix4x4_t tempmatrix;
1044 if (info->trailspacing > 0)
1045 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1047 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1048 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);
1053 if (info->tex[1] > info->tex[0])
1055 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1056 tex = min(tex, info->tex[1] - 1);
1058 if (info->particletype == pt_decal)
1059 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]);
1060 else if (info->particletype == pt_beam)
1061 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);
1064 if (!cl_particles.integer)
1066 switch (info->particletype)
1068 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1069 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1070 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1071 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1074 VectorCopy(originmins, trailpos);
1075 if (info->trailspacing > 0)
1077 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1078 trailstep = info->trailspacing / cl_particles_quality.value;
1082 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1085 for (;info->particleaccumulator > 0;info->particleaccumulator--)
1087 if (info->tex[1] > info->tex[0])
1089 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1090 tex = min(tex, info->tex[1] - 1);
1094 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1095 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1096 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1099 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);
1101 VectorMA(trailpos, trailstep, traildir, trailpos);
1108 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor);
1116 void CL_EntityParticles (const entity_t *ent)
1119 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1120 static vec3_t avelocities[NUMVERTEXNORMALS];
1121 if (!cl_particles.integer) return;
1123 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1125 if (!avelocities[0][0])
1126 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1127 avelocities[0][i] = lhrandom(0, 2.55);
1129 for (i = 0;i < NUMVERTEXNORMALS;i++)
1131 yaw = cl.time * avelocities[i][0];
1132 pitch = cl.time * avelocities[i][1];
1133 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1134 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1135 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1136 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);
1141 void CL_ReadPointFile_f (void)
1143 vec3_t org, leakorg;
1145 char *pointfile = NULL, *pointfilepos, *t, tchar;
1146 char name[MAX_OSPATH];
1151 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1152 strlcat (name, ".pts", sizeof (name));
1153 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1156 Con_Printf("Could not open %s\n", name);
1160 Con_Printf("Reading %s...\n", name);
1161 VectorClear(leakorg);
1164 pointfilepos = pointfile;
1165 while (*pointfilepos)
1167 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1172 while (*t && *t != '\n' && *t != '\r')
1176 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1182 VectorCopy(org, leakorg);
1185 if (cl.num_particles < cl.max_particles - 3)
1188 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);
1191 Mem_Free(pointfile);
1192 VectorCopy(leakorg, org);
1193 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1195 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);
1196 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);
1197 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);
1202 CL_ParseParticleEffect
1204 Parse an effect out of the server message
1207 void CL_ParseParticleEffect (void)
1210 int i, count, msgcount, color;
1212 MSG_ReadVector(org, cls.protocol);
1213 for (i=0 ; i<3 ; i++)
1214 dir[i] = MSG_ReadChar ();
1215 msgcount = MSG_ReadByte ();
1216 color = MSG_ReadByte ();
1218 if (msgcount == 255)
1223 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1228 CL_ParticleExplosion
1232 void CL_ParticleExplosion (const vec3_t org)
1238 if (cl_stainmaps.integer)
1239 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1240 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1242 if (cl_particles_quake.integer)
1244 for (i = 0;i < 1024;i++)
1250 color = particlepalette[ramp1[r]];
1251 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);
1255 color = particlepalette[ramp2[r]];
1256 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);
1262 i = CL_PointSuperContents(org);
1263 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1265 if (cl_particles.integer && cl_particles_bubbles.integer)
1266 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1267 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);
1271 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1273 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1275 for (i = 0;i < 32;i++)
1279 for (k = 0;k < 16;k++)
1281 v[0] = org[0] + lhrandom(-48, 48);
1282 v[1] = org[1] + lhrandom(-48, 48);
1283 v[2] = org[2] + lhrandom(-48, 48);
1284 trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
1285 if (trace.fraction >= 0.1)
1288 VectorSubtract(trace.endpos, org, v2);
1289 VectorScale(v2, 2.0f, v2);
1290 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);
1294 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1295 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1296 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);
1300 if (cl_particles_explosions_shell.integer)
1301 R_NewExplosion(org);
1306 CL_ParticleExplosion2
1310 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1313 if (!cl_particles.integer) return;
1315 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1317 k = particlepalette[colorStart + (i % colorLength)];
1318 if (cl_particles_quake.integer)
1319 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);
1321 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);
1325 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1327 if (cl_particles_sparks.integer)
1329 sparkcount *= cl_particles_quality.value;
1330 while(sparkcount-- > 0)
1331 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);
1333 if (cl_particles_smoke.integer)
1335 smokecount *= cl_particles_quality.value;
1336 while(smokecount-- > 0)
1337 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);
1341 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)
1344 if (!cl_particles.integer) return;
1346 count = (int)(count * cl_particles_quality.value);
1349 k = particlepalette[colorbase + (rand()&3)];
1350 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);
1354 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1357 float z, minz, maxz;
1359 if (!cl_particles.integer) return;
1360 if (dir[2] < 0) // falling
1365 minz = z - fabs(dir[2]) * 0.1;
1366 maxz = z + fabs(dir[2]) * 0.1;
1367 minz = bound(mins[2], minz, maxs[2]);
1368 maxz = bound(mins[2], maxz, maxs[2]);
1370 count = (int)(count * cl_particles_quality.value);
1375 count *= 4; // ick, this should be in the mod or maps?
1379 k = particlepalette[colorbase + (rand()&3)];
1380 if (gamemode == GAME_GOODVSBAD2)
1381 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);
1383 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);
1389 k = particlepalette[colorbase + (rand()&3)];
1390 if (gamemode == GAME_GOODVSBAD2)
1391 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);
1393 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);
1395 VectorCopy(p->vel, p->relativedirection);
1399 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1408 void CL_MoveParticles (void)
1411 int i, maxparticle, j, a, content;
1412 float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
1413 particletype_t *decaltype, *bloodtype;
1417 // LordHavoc: early out condition
1418 if (!cl.num_particles)
1420 cl.free_particle = 0;
1424 frametime = cl.time - cl.oldtime;
1425 gravity = frametime * sv_gravity.value;
1426 dvel = 1+4*frametime;
1427 decalfade = frametime * 255 / cl_decals_fadetime.value;
1428 decaltype = particletype + pt_decal;
1429 bloodtype = particletype + pt_blood;
1433 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1437 if (cl.free_particle > i)
1438 cl.free_particle = i;
1443 // heavily optimized decal case
1444 if (p->type == decaltype)
1446 // FIXME: this has fairly wacky handling of alpha
1447 if (cl.time > p->time2 + cl_decals_time.value)
1449 p->alpha -= decalfade;
1453 if (cl.free_particle > i)
1454 cl.free_particle = i;
1460 if (cl.entities[p->owner].render.model == p->ownermodel)
1462 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1463 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1468 if (cl.free_particle > i)
1469 cl.free_particle = i;
1477 p->alpha -= p->alphafade * frametime;
1482 if (cl.free_particle > i)
1483 cl.free_particle = i;
1487 if (p->type->orientation != PARTICLE_BEAM)
1489 VectorCopy(p->org, oldorg);
1490 VectorMA(p->org, frametime, p->vel, p->org);
1491 VectorCopy(p->org, org);
1494 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);
1495 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1496 // or if the trace hit something flagged as NOIMPACT
1497 // then remove the particle
1498 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1503 // react if the particle hit something
1504 if (trace.fraction < 1)
1506 VectorCopy(trace.endpos, p->org);
1507 if (p->type == particletype + pt_rain)
1509 // raindrop - splash on solid/water/slime/lava
1511 // convert from a raindrop particle to a rainsplash decal
1512 VectorCopy(trace.plane.normal, p->vel);
1513 VectorAdd(p->org, p->vel, p->org);
1514 p->type = particletype + pt_raindecal;
1515 p->texnum = tex_rainsplash;
1517 p->alphafade = p->alpha / 0.4;
1520 p->liquidfriction = 0;
1523 p->sizeincrease = p->size * 16;
1526 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);
1528 else if (p->type == bloodtype)
1530 // blood - splash on solid
1531 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1536 if (!cl_decals.integer)
1541 // convert from a blood particle to a blood decal
1542 VectorCopy(trace.plane.normal, p->vel);
1543 VectorAdd(p->org, p->vel, p->org);
1544 if (cl_stainmaps.integer)
1545 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)));
1547 p->type = particletype + pt_decal;
1548 p->texnum = tex_blooddecal[rand()&7];
1550 p->ownermodel = cl.entities[hitent].render.model;
1551 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1552 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1553 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1558 p->liquidfriction = 0;
1562 else if (p->bounce < 0)
1564 // bounce -1 means remove on impact
1570 // anything else - bounce off solid
1571 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1572 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1573 if (DotProduct(p->vel, p->vel) < 0.03)
1574 VectorClear(p->vel);
1578 p->vel[2] -= p->gravity * gravity;
1580 if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1582 f = 1.0f - min(p->liquidfriction * frametime, 1);
1583 VectorScale(p->vel, f, p->vel);
1585 else if (p->airfriction)
1587 f = 1.0f - min(p->airfriction * frametime, 1);
1588 VectorScale(p->vel, f, p->vel);
1592 if (p->type != particletype + pt_static)
1594 switch (p->type - particletype)
1596 case pt_entityparticle:
1597 // particle that removes itself after one rendered frame
1604 a = CL_PointSuperContents(p->org);
1605 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1607 p->size += frametime * 8;
1608 //p->alpha -= bloodwaterfade;
1611 p->vel[2] -= gravity;
1612 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1616 a = CL_PointSuperContents(p->org);
1617 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1624 a = CL_PointSuperContents(p->org);
1625 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1629 if (cl.time > p->time2)
1632 p->time2 = cl.time + (rand() & 3) * 0.1;
1633 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1634 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1635 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1637 a = CL_PointSuperContents(p->org);
1638 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1646 cl.num_particles = maxparticle + 1;
1649 #define MAX_PARTICLETEXTURES 64
1650 // particletexture_t is a rectangle in the particlefonttexture
1651 typedef struct particletexture_s
1653 rtexture_t *texture;
1654 float s1, t1, s2, t2;
1658 static rtexturepool_t *particletexturepool;
1659 static rtexture_t *particlefonttexture;
1660 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1662 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1664 #define PARTICLETEXTURESIZE 64
1665 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1667 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1671 dz = 1 - (dx*dx+dy*dy);
1672 if (dz > 0) // it does hit the sphere
1676 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1677 VectorNormalize(normal);
1678 dot = DotProduct(normal, light);
1679 if (dot > 0.5) // interior reflection
1680 f += ((dot * 2) - 1);
1681 else if (dot < -0.5) // exterior reflection
1682 f += ((dot * -2) - 1);
1684 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1685 VectorNormalize(normal);
1686 dot = DotProduct(normal, light);
1687 if (dot > 0.5) // interior reflection
1688 f += ((dot * 2) - 1);
1689 else if (dot < -0.5) // exterior reflection
1690 f += ((dot * -2) - 1);
1692 f += 16; // just to give it a haze so you can see the outline
1693 f = bound(0, f, 255);
1694 return (unsigned char) f;
1700 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1702 int basex, basey, y;
1703 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1704 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1705 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1706 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1709 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1712 float cx, cy, dx, dy, f, iradius;
1714 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1715 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1716 iradius = 1.0f / radius;
1717 alpha *= (1.0f / 255.0f);
1718 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1720 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1724 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1727 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1728 d[0] += (int)(f * (red - d[0]));
1729 d[1] += (int)(f * (green - d[1]));
1730 d[2] += (int)(f * (blue - d[2]));
1736 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1739 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1741 data[0] = bound(minr, data[0], maxr);
1742 data[1] = bound(ming, data[1], maxg);
1743 data[2] = bound(minb, data[2], maxb);
1747 void particletextureinvert(unsigned char *data)
1750 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1752 data[0] = 255 - data[0];
1753 data[1] = 255 - data[1];
1754 data[2] = 255 - data[2];
1758 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1759 static void R_InitBloodTextures (unsigned char *particletexturedata)
1762 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1765 for (i = 0;i < 8;i++)
1767 memset(&data[0][0][0], 255, sizeof(data));
1768 for (k = 0;k < 24;k++)
1769 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1770 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1771 particletextureinvert(&data[0][0][0]);
1772 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1776 for (i = 0;i < 8;i++)
1778 memset(&data[0][0][0], 255, sizeof(data));
1780 for (j = 1;j < 10;j++)
1781 for (k = min(j, m - 1);k < m;k++)
1782 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1783 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1784 particletextureinvert(&data[0][0][0]);
1785 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1790 //uncomment this to make engine save out particle font to a tga file when run
1791 //#define DUMPPARTICLEFONT
1793 static void R_InitParticleTexture (void)
1795 int x, y, d, i, k, m;
1799 // a note: decals need to modulate (multiply) the background color to
1800 // properly darken it (stain), and they need to be able to alpha fade,
1801 // this is a very difficult challenge because it means fading to white
1802 // (no change to background) rather than black (darkening everything
1803 // behind the whole decal polygon), and to accomplish this the texture is
1804 // inverted (dark red blood on white background becomes brilliant cyan
1805 // and white on black background) so we can alpha fade it to black, then
1806 // we invert it again during the blendfunc to make it work...
1808 #ifndef DUMPPARTICLEFONT
1809 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1810 if (!particlefonttexture)
1813 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1814 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1815 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1818 for (i = 0;i < 8;i++)
1820 memset(&data[0][0][0], 255, sizeof(data));
1823 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1825 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1826 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1828 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1830 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1831 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1833 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1834 d = (noise2[y][x] - 128) * 3 + 192;
1836 d = (int)(d * (1-(dx*dx+dy*dy)));
1837 d = (d * noise1[y][x]) >> 7;
1838 d = bound(0, d, 255);
1839 data[y][x][3] = (unsigned char) d;
1846 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1850 memset(&data[0][0][0], 255, sizeof(data));
1851 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1853 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1854 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1856 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1857 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1858 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1861 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1864 memset(&data[0][0][0], 255, sizeof(data));
1865 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1867 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1868 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1870 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1871 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1872 d = bound(0, d, 255);
1873 data[y][x][3] = (unsigned char) d;
1876 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1879 memset(&data[0][0][0], 255, sizeof(data));
1880 light[0] = 1;light[1] = 1;light[2] = 1;
1881 VectorNormalize(light);
1882 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1884 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1885 // stretch upper half of bubble by +50% and shrink lower half by -50%
1886 // (this gives an elongated teardrop shape)
1888 dy = (dy - 0.5f) * 2.0f;
1890 dy = (dy - 0.5f) / 1.5f;
1891 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1893 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1894 // shrink bubble width to half
1896 data[y][x][3] = shadebubble(dx, dy, light);
1899 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1902 memset(&data[0][0][0], 255, sizeof(data));
1903 light[0] = 1;light[1] = 1;light[2] = 1;
1904 VectorNormalize(light);
1905 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1907 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1908 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1910 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1911 data[y][x][3] = shadebubble(dx, dy, light);
1914 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1916 // Blood particles and blood decals
1917 R_InitBloodTextures (particletexturedata);
1920 for (i = 0;i < 8;i++)
1922 memset(&data[0][0][0], 255, sizeof(data));
1923 for (k = 0;k < 12;k++)
1924 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1925 for (k = 0;k < 3;k++)
1926 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1927 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1928 particletextureinvert(&data[0][0][0]);
1929 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1932 #ifdef DUMPPARTICLEFONT
1933 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1936 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1938 Mem_Free(particletexturedata);
1940 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1942 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1943 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1944 particletexture[i].texture = particlefonttexture;
1945 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1946 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1947 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1948 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1951 #ifndef DUMPPARTICLEFONT
1952 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1953 if (!particletexture[tex_beam].texture)
1956 unsigned char noise3[64][64], data2[64][16][4];
1958 fractalnoise(&noise3[0][0], 64, 4);
1960 for (y = 0;y < 64;y++)
1962 dy = (y - 0.5f*64) / (64*0.5f-1);
1963 for (x = 0;x < 16;x++)
1965 dx = (x - 0.5f*16) / (16*0.5f-2);
1966 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1967 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1968 data2[y][x][3] = 255;
1972 #ifdef DUMPPARTICLEFONT
1973 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1975 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1977 particletexture[tex_beam].s1 = 0;
1978 particletexture[tex_beam].t1 = 0;
1979 particletexture[tex_beam].s2 = 1;
1980 particletexture[tex_beam].t2 = 1;
1983 static void r_part_start(void)
1985 particletexturepool = R_AllocTexturePool();
1986 R_InitParticleTexture ();
1987 CL_Particles_LoadEffectInfo();
1990 static void r_part_shutdown(void)
1992 R_FreeTexturePool(&particletexturepool);
1995 static void r_part_newmap(void)
1999 #define BATCHSIZE 256
2000 int particle_element3i[BATCHSIZE*6];
2001 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2003 void R_Particles_Init (void)
2006 for (i = 0;i < BATCHSIZE;i++)
2008 particle_element3i[i*6+0] = i*4+0;
2009 particle_element3i[i*6+1] = i*4+1;
2010 particle_element3i[i*6+2] = i*4+2;
2011 particle_element3i[i*6+3] = i*4+0;
2012 particle_element3i[i*6+4] = i*4+2;
2013 particle_element3i[i*6+5] = i*4+3;
2016 Cvar_RegisterVariable(&r_drawparticles);
2017 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2020 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2022 int surfacelistindex;
2023 int batchstart, batchcount;
2024 const particle_t *p;
2026 rtexture_t *texture;
2027 float *v3f, *t2f, *c4f;
2029 R_Mesh_Matrix(&identitymatrix);
2030 R_Mesh_ResetTextureState();
2031 R_Mesh_VertexPointer(particle_vertex3f);
2032 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f);
2033 R_Mesh_ColorPointer(particle_color4f);
2034 GL_DepthMask(false);
2037 // first generate all the vertices at once
2038 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2040 particletexture_t *tex;
2042 float up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
2044 p = cl.particles + surfacelist[surfacelistindex];
2046 blendmode = p->type->blendmode;
2048 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2049 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2050 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2051 ca = p->alpha * (1.0f / 255.0f);
2052 if (blendmode == PBLEND_MOD)
2062 ca /= cl_particles_quality.value;
2063 if (p->type->lighting)
2065 float ambient[3], diffuse[3], diffusenormal[3];
2066 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2067 cr *= (ambient[0] + 0.5 * diffuse[0]);
2068 cg *= (ambient[1] + 0.5 * diffuse[1]);
2069 cb *= (ambient[2] + 0.5 * diffuse[2]);
2071 if (r_refdef.fogenabled)
2073 fog = VERTEXFOGTABLE(VectorDistance(p->org, r_view.origin));
2078 if (blendmode == PBLEND_ALPHA)
2080 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2081 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2082 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2085 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2086 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2087 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2088 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2090 size = p->size * cl_particles_size.value;
2092 tex = &particletexture[p->texnum];
2093 if (p->type->orientation == PARTICLE_BILLBOARD)
2095 VectorScale(r_view.left, -size, right);
2096 VectorScale(r_view.up, size, up);
2097 v3f[ 0] = org[0] - right[0] - up[0];
2098 v3f[ 1] = org[1] - right[1] - up[1];
2099 v3f[ 2] = org[2] - right[2] - up[2];
2100 v3f[ 3] = org[0] - right[0] + up[0];
2101 v3f[ 4] = org[1] - right[1] + up[1];
2102 v3f[ 5] = org[2] - right[2] + up[2];
2103 v3f[ 6] = org[0] + right[0] + up[0];
2104 v3f[ 7] = org[1] + right[1] + up[1];
2105 v3f[ 8] = org[2] + right[2] + up[2];
2106 v3f[ 9] = org[0] + right[0] - up[0];
2107 v3f[10] = org[1] + right[1] - up[1];
2108 v3f[11] = org[2] + right[2] - up[2];
2109 t2f[0] = tex->s1;t2f[1] = tex->t2;
2110 t2f[2] = tex->s1;t2f[3] = tex->t1;
2111 t2f[4] = tex->s2;t2f[5] = tex->t1;
2112 t2f[6] = tex->s2;t2f[7] = tex->t2;
2114 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2117 if (DotProduct(p->vel, r_view.origin) > DotProduct(p->vel, org))
2119 VectorNegate(p->vel, v);
2120 VectorVectors(v, right, up);
2123 VectorVectors(p->vel, right, up);
2124 VectorScale(right, size, right);
2125 VectorScale(up, size, up);
2126 v3f[ 0] = org[0] - right[0] - up[0];
2127 v3f[ 1] = org[1] - right[1] - up[1];
2128 v3f[ 2] = org[2] - right[2] - up[2];
2129 v3f[ 3] = org[0] - right[0] + up[0];
2130 v3f[ 4] = org[1] - right[1] + up[1];
2131 v3f[ 5] = org[2] - right[2] + up[2];
2132 v3f[ 6] = org[0] + right[0] + up[0];
2133 v3f[ 7] = org[1] + right[1] + up[1];
2134 v3f[ 8] = org[2] + right[2] + up[2];
2135 v3f[ 9] = org[0] + right[0] - up[0];
2136 v3f[10] = org[1] + right[1] - up[1];
2137 v3f[11] = org[2] + right[2] - up[2];
2138 t2f[0] = tex->s1;t2f[1] = tex->t2;
2139 t2f[2] = tex->s1;t2f[3] = tex->t1;
2140 t2f[4] = tex->s2;t2f[5] = tex->t1;
2141 t2f[6] = tex->s2;t2f[7] = tex->t2;
2143 else if (p->type->orientation == PARTICLE_SPARK)
2145 VectorMA(org, -0.02, p->vel, v);
2146 VectorMA(org, 0.02, p->vel, up2);
2147 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2148 t2f[0] = tex->s1;t2f[1] = tex->t2;
2149 t2f[2] = tex->s1;t2f[3] = tex->t1;
2150 t2f[4] = tex->s2;t2f[5] = tex->t1;
2151 t2f[6] = tex->s2;t2f[7] = tex->t2;
2153 else if (p->type->orientation == PARTICLE_BEAM)
2155 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2156 VectorSubtract(p->vel, org, up);
2157 VectorNormalize(up);
2158 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2159 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2160 t2f[0] = 1;t2f[1] = v[0];
2161 t2f[2] = 0;t2f[3] = v[0];
2162 t2f[4] = 0;t2f[5] = v[1];
2163 t2f[6] = 1;t2f[7] = v[1];
2167 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2172 // now render batches of particles based on blendmode and texture
2173 blendmode = PBLEND_ADD;
2174 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2175 texture = particletexture[63].texture;
2176 R_Mesh_TexBind(0, R_GetTexture(texture));
2177 GL_LockArrays(0, numsurfaces*4);
2180 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2182 p = cl.particles + surfacelist[surfacelistindex];
2184 if (blendmode != p->type->blendmode)
2187 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2189 batchstart = surfacelistindex;
2190 blendmode = p->type->blendmode;
2191 if (blendmode == PBLEND_ALPHA)
2192 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2193 else if (blendmode == PBLEND_ADD)
2194 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2195 else //if (blendmode == PBLEND_MOD)
2196 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2198 if (texture != particletexture[p->texnum].texture)
2201 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2203 batchstart = surfacelistindex;
2204 texture = particletexture[p->texnum].texture;
2205 R_Mesh_TexBind(0, R_GetTexture(texture));
2211 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2212 GL_LockArrays(0, 0);
2215 void R_DrawParticles (void)
2218 float minparticledist;
2221 // LordHavoc: early out conditions
2222 if ((!cl.num_particles) || (!r_drawparticles.integer))
2225 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2227 // LordHavoc: only render if not too close
2228 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2232 r_refdef.stats.particles++;
2233 if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2234 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);