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"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
31 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
32 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
33 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
34 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
35 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
36 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
38 {PBLEND_MOD, PARTICLE_BILLBOARD, false}, //pt_blood
39 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
40 {PBLEND_MOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
41 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
44 #define PARTICLEEFFECT_UNDERWATER 1
45 #define PARTICLEEFFECT_NOTUNDERWATER 2
47 typedef struct particleeffectinfo_s
49 int effectnameindex; // which effect this belongs to
50 // PARTICLEEFFECT_* bits
52 // blood effects may spawn very few particles, so proper fraction-overflow
53 // handling is very important, this variable keeps track of the fraction
54 double particleaccumulator;
55 // the math is: countabsolute + requestedcount * countmultiplier * quality
56 // absolute number of particles to spawn, often used for decals
57 // (unaffected by quality and requestedcount)
59 // multiplier for the number of particles CL_ParticleEffect was told to
60 // spawn, most effects do not really have a count and hence use 1, so
61 // this is often the actual count to spawn, not merely a multiplier
62 float countmultiplier;
63 // if > 0 this causes the particle to spawn in an evenly spaced line from
64 // originmins to originmaxs (causing them to describe a trail, not a box)
66 // type of particle to spawn (defines some aspects of behavior)
68 // range of colors to choose from in hex RRGGBB (like HTML color tags),
69 // randomly interpolated at spawn
70 unsigned int color[2];
71 // a random texture is chosen in this range (note the second value is one
72 // past the last choosable, so for example 8,16 chooses any from 8 up and
74 // if start and end of the range are the same, no randomization is done
76 // range of size values randomly chosen when spawning, plus size increase over time
78 // range of alpha values randomly chosen when spawning, plus alpha fade
80 // how long the particle should live (note it is also removed if alpha drops to 0)
82 // how much gravity affects this particle (negative makes it fly up!)
84 // how much bounce the particle has when it hits a surface
85 // if negative the particle is removed on impact
87 // if in air this friction is applied
88 // if negative the particle accelerates
90 // if in liquid (water/slime/lava) this friction is applied
91 // if negative the particle accelerates
93 // these offsets are added to the values given to particleeffect(), and
94 // then an ellipsoid-shaped jitter is added as defined by these
95 // (they are the 3 radii)
96 float originoffset[3];
97 float velocityoffset[3];
98 float originjitter[3];
99 float velocityjitter[3];
100 float velocitymultiplier;
101 // an effect can also spawn a dlight
102 float lightradiusstart;
103 float lightradiusfade;
106 qboolean lightshadow;
109 particleeffectinfo_t;
111 #define MAX_PARTICLEEFFECTNAME 256
112 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
114 #define MAX_PARTICLEEFFECTINFO 4096
116 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
118 static int particlepalette[256] =
120 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
121 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
122 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
123 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
124 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
125 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
126 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
127 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
128 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
129 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
130 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
131 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
132 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
133 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
134 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
135 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
136 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
137 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
138 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
139 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
140 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
141 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
142 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
143 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
144 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
145 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
146 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
147 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
148 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
149 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
150 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
151 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
154 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
155 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
156 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
158 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
160 // texture numbers in particle font
161 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
162 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
163 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
164 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
165 static const int tex_rainsplash = 32;
166 static const int tex_particle = 63;
167 static const int tex_bubble = 62;
168 static const int tex_raindrop = 61;
169 static const int tex_beam = 60;
171 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
172 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles and reduces their alpha"};
173 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
174 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
175 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
176 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "0.5", "opacity of blood"};
177 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
178 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
179 cvar_t cl_particles_explosions_smoke = {CVAR_SAVE, "cl_particles_explosions_smokes", "0", "enables smoke from explosions"};
180 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
181 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
182 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
183 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
184 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
185 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
186 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
187 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
188 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
189 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "0", "enables decals (bullet holes, blood, etc)"};
190 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "0", "how long before decals start to fade away"};
191 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "20", "how long decals take to fade away"};
194 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
200 particleeffectinfo_t *info = NULL;
201 const char *text = textstart;
203 effectinfoindex = -1;
204 for (linenumber = 1;;linenumber++)
207 for (arrayindex = 0;arrayindex < 16;arrayindex++)
208 argv[arrayindex][0] = 0;
211 if (!COM_ParseToken(&text, true))
213 if (!strcmp(com_token, "\n"))
217 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
223 #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;}
224 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
225 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
226 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
227 #define readfloat(var) checkparms(2);var = atof(argv[1])
228 if (!strcmp(argv[0], "effect"))
233 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
235 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
238 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
240 if (particleeffectname[effectnameindex][0])
242 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
247 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
251 // if we run out of names, abort
252 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
254 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
257 info = particleeffectinfo + effectinfoindex;
258 info->effectnameindex = effectnameindex;
259 info->particletype = pt_alphastatic;
260 info->tex[0] = tex_particle;
261 info->tex[1] = tex_particle;
262 info->color[0] = 0xFFFFFF;
263 info->color[1] = 0xFFFFFF;
267 info->alpha[1] = 256;
268 info->alpha[2] = 256;
269 info->time[0] = 9999;
270 info->time[1] = 9999;
271 VectorSet(info->lightcolor, 1, 1, 1);
272 info->lightshadow = true;
273 info->lighttime = 9999;
275 else if (info == NULL)
277 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
280 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
281 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
282 else if (!strcmp(argv[0], "type"))
285 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
286 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
287 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
288 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
289 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
290 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
291 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
292 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
293 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
294 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
295 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
296 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
297 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
299 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
300 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
301 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
302 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
303 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
304 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
305 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
306 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
307 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
308 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
309 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
310 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
311 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
312 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
313 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
314 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
315 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
316 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
317 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
318 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
319 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
320 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
321 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
322 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
324 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
333 int CL_ParticleEffectIndexForName(const char *name)
336 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
337 if (!strcmp(particleeffectname[i], name))
342 const char *CL_ParticleEffectNameForIndex(int i)
344 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
346 return particleeffectname[i];
349 // MUST match effectnameindex_t in client.h
350 static const char *standardeffectnames[EFFECT_TOTAL] =
374 "TE_TEI_BIGEXPLOSION",
390 void CL_Particles_LoadEffectInfo(void)
393 unsigned char *filedata;
394 fs_offset_t filesize;
395 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
396 memset(particleeffectname, 0, sizeof(particleeffectname));
397 for (i = 0;i < EFFECT_TOTAL;i++)
398 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
399 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
402 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
412 void CL_ReadPointFile_f (void);
413 void CL_Particles_Init (void)
415 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)");
416 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
418 Cvar_RegisterVariable (&cl_particles);
419 Cvar_RegisterVariable (&cl_particles_quality);
420 Cvar_RegisterVariable (&cl_particles_size);
421 Cvar_RegisterVariable (&cl_particles_quake);
422 Cvar_RegisterVariable (&cl_particles_blood);
423 Cvar_RegisterVariable (&cl_particles_blood_alpha);
424 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
425 Cvar_RegisterVariable (&cl_particles_explosions_smoke);
426 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
427 Cvar_RegisterVariable (&cl_particles_explosions_shell);
428 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
429 Cvar_RegisterVariable (&cl_particles_rain);
430 Cvar_RegisterVariable (&cl_particles_snow);
431 Cvar_RegisterVariable (&cl_particles_smoke);
432 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
433 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
434 Cvar_RegisterVariable (&cl_particles_sparks);
435 Cvar_RegisterVariable (&cl_particles_bubbles);
436 Cvar_RegisterVariable (&cl_decals);
437 Cvar_RegisterVariable (&cl_decals_time);
438 Cvar_RegisterVariable (&cl_decals_fadetime);
441 void CL_Particles_Shutdown (void)
445 // list of all 26 parameters:
446 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
447 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
448 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
449 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
450 // palpha - opacity of particle as 0-255 (can be more than 255)
451 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
452 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
453 // pgravity - how much effect gravity has on the particle (0-1)
454 // 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
455 // px,py,pz - starting origin of particle
456 // pvx,pvy,pvz - starting velocity of particle
457 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
458 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)
463 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].type;cl.free_particle++);
464 if (cl.free_particle >= cl.max_particles)
466 part = &cl.particles[cl.free_particle++];
467 if (cl.num_particles < cl.free_particle)
468 cl.num_particles = cl.free_particle;
469 memset(part, 0, sizeof(*part));
471 l2 = (int)lhrandom(0.5, 256.5);
473 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
474 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
475 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
476 part->color[3] = 0xFF;
479 part->sizeincrease = psizeincrease;
480 part->alpha = palpha;
481 part->alphafade = palphafade;
482 part->gravity = pgravity;
483 part->bounce = pbounce;
485 part->org[0] = px + originjitter * v[0];
486 part->org[1] = py + originjitter * v[1];
487 part->org[2] = pz + originjitter * v[2];
488 part->vel[0] = pvx + velocityjitter * v[0];
489 part->vel[1] = pvy + velocityjitter * v[1];
490 part->vel[2] = pvz + velocityjitter * v[2];
492 part->airfriction = pairfriction;
493 part->liquidfriction = pliquidfriction;
497 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
500 if (!cl_decals.integer)
502 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);
507 p->ownermodel = cl.entities[p->owner].render.model;
508 VectorAdd(org, normal, p->org);
509 VectorCopy(normal, p->vel);
510 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
511 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
512 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
516 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
519 float bestfrac, bestorg[3], bestnormal[3];
521 int besthitent = 0, hitent;
524 for (i = 0;i < 32;i++)
527 VectorMA(org, maxdist, org2, org2);
528 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
529 // take the closest trace result that doesn't end up hitting a NOMARKS
530 // surface (sky for example)
531 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
533 bestfrac = trace.fraction;
535 VectorCopy(trace.endpos, bestorg);
536 VectorCopy(trace.plane.normal, bestnormal);
540 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
543 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
544 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
545 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, qboolean spawndlight, qboolean spawnparticles)
548 matrix4x4_t tempmatrix;
549 VectorLerp(originmins, 0.5, originmaxs, center);
550 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
551 if (effectnameindex == EFFECT_SVC_PARTICLE)
553 if (cl_particles.integer)
555 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
557 CL_ParticleExplosion(center);
558 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
559 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
562 count *= cl_particles_quality.value;
563 for (;count > 0;count--)
565 int k = particlepalette[palettecolor + (rand()&7)];
566 if (cl_particles_quake.integer)
567 particle(particletype + pt_alphastatic, k, k, tex_particle, 1.5, 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);
568 else if (gamemode == GAME_GOODVSBAD2)
569 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);
571 particle(particletype + pt_alphastatic, k, k, tex_particle, 1.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, 8, 15);
576 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
577 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
578 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
579 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
580 else if (effectnameindex == EFFECT_TE_SPIKE)
582 if (cl_particles_bulletimpacts.integer)
584 if (cl_particles_quake.integer)
586 if (cl_particles_smoke.integer)
587 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
590 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
593 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
594 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
596 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
598 if (cl_particles_bulletimpacts.integer)
600 if (cl_particles_quake.integer)
602 if (cl_particles_smoke.integer)
603 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
606 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
609 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
610 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
611 CL_AllocLightFlash(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);
613 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
615 if (cl_particles_bulletimpacts.integer)
617 if (cl_particles_quake.integer)
619 if (cl_particles_smoke.integer)
620 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
623 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
626 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
627 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
629 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
631 if (cl_particles_bulletimpacts.integer)
633 if (cl_particles_quake.integer)
635 if (cl_particles_smoke.integer)
636 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
639 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
642 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
643 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
644 CL_AllocLightFlash(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);
646 else if (effectnameindex == EFFECT_TE_BLOOD)
648 if (!cl_particles_blood.integer)
650 if (cl_particles_quake.integer)
651 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
654 static double bloodaccumulator = 0;
655 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
656 for (;bloodaccumulator > 0;bloodaccumulator--)
657 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);
660 else if (effectnameindex == EFFECT_TE_SPARK)
661 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
662 else if (effectnameindex == EFFECT_TE_PLASMABURN)
664 // plasma scorch mark
665 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
666 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
667 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
669 else if (effectnameindex == EFFECT_TE_GUNSHOT)
671 if (cl_particles_bulletimpacts.integer)
673 if (cl_particles_quake.integer)
674 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
676 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
679 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
680 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
682 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
684 if (cl_particles_bulletimpacts.integer)
686 if (cl_particles_quake.integer)
687 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
689 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
692 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
693 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
694 CL_AllocLightFlash(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);
696 else if (effectnameindex == EFFECT_TE_EXPLOSION)
698 CL_ParticleExplosion(center);
699 CL_AllocLightFlash(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);
701 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
703 CL_ParticleExplosion(center);
704 CL_AllocLightFlash(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);
706 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
708 if (cl_particles_quake.integer)
711 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
714 particle(particletype + pt_static, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256);
716 particle(particletype + pt_static, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0);
720 CL_ParticleExplosion(center);
721 CL_AllocLightFlash(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);
723 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
724 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
725 else if (effectnameindex == EFFECT_TE_FLAMEJET)
727 count *= cl_particles_quality.value;
729 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);
731 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
733 float i, j, inc, vel;
736 inc = 8 / cl_particles_quality.value;
737 for (i = -128;i < 128;i += inc)
739 for (j = -128;j < 128;j += inc)
741 dir[0] = j + lhrandom(0, inc);
742 dir[1] = i + lhrandom(0, inc);
744 org[0] = center[0] + dir[0];
745 org[1] = center[1] + dir[1];
746 org[2] = center[2] + lhrandom(0, 64);
747 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
748 particle(particletype + pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 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);
752 else if (effectnameindex == EFFECT_TE_TELEPORT)
754 float i, j, k, inc, vel;
757 inc = 8 / cl_particles_quality.value;
758 for (i = -16;i < 16;i += inc)
760 for (j = -16;j < 16;j += inc)
762 for (k = -24;k < 32;k += inc)
764 VectorSet(dir, i*8, j*8, k*8);
765 VectorNormalize(dir);
766 vel = lhrandom(50, 113);
767 particle(particletype + pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 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);
771 particle(particletype + pt_static, particlepalette[14], particlepalette[14], tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0);
772 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
774 else if (effectnameindex == EFFECT_TE_TEI_G3)
775 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);
776 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
778 if (cl_particles_smoke.integer)
780 count *= 0.25f * cl_particles_quality.value;
782 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);
785 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
787 CL_ParticleExplosion(center);
788 CL_AllocLightFlash(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);
790 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
793 if (cl_stainmaps.integer)
794 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
795 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
796 if (cl_particles_smoke.integer)
797 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
798 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);
799 if (cl_particles_sparks.integer)
800 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
801 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);
802 CL_AllocLightFlash(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);
804 else if (effectnameindex == EFFECT_EF_FLAME)
806 count *= 300 * cl_particles_quality.value;
808 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);
809 CL_AllocLightFlash(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);
811 else if (effectnameindex == EFFECT_EF_STARDUST)
813 count *= 200 * cl_particles_quality.value;
815 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);
816 CL_AllocLightFlash(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);
818 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
822 int smoke, blood, bubbles, r, color;
824 if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
827 Vector4Set(light, 0, 0, 0, 0);
829 if (effectnameindex == EFFECT_TR_ROCKET)
830 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
831 else if (effectnameindex == EFFECT_TR_VORESPIKE)
833 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
834 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
836 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
838 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
839 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
843 matrix4x4_t tempmatrix;
844 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
845 R_RTLight_Update(&r_refdef.lights[r_refdef.numlights++], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
852 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
855 VectorSubtract(originmaxs, originmins, dir);
856 len = VectorNormalizeLength(dir);
859 dec = -ent->persistent.trail_time;
860 ent->persistent.trail_time += len;
861 if (ent->persistent.trail_time < 0.01f)
864 // if we skip out, leave it reset
865 ent->persistent.trail_time = 0.0f;
870 // advance into this frame to reach the first puff location
871 VectorMA(originmins, dec, dir, pos);
874 smoke = cl_particles.integer && cl_particles_smoke.integer;
875 blood = cl_particles.integer && cl_particles_blood.integer;
876 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
877 qd = 1.0f / cl_particles_quality.value;
884 if (effectnameindex == EFFECT_TR_BLOOD)
886 if (cl_particles_quake.integer)
888 color = particlepalette[67 + (rand()&3)];
889 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
894 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);
897 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
899 if (cl_particles_quake.integer)
902 color = particlepalette[67 + (rand()&3)];
903 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
908 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);
914 if (effectnameindex == EFFECT_TR_ROCKET)
916 if (cl_particles_quake.integer)
919 color = particlepalette[ramp3[r]];
920 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
924 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);
925 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);
928 else if (effectnameindex == EFFECT_TR_GRENADE)
930 if (cl_particles_quake.integer)
933 color = particlepalette[ramp3[r]];
934 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
938 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);
941 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
943 if (cl_particles_quake.integer)
946 color = particlepalette[52 + (rand()&7)];
947 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
948 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
950 else if (gamemode == GAME_GOODVSBAD2)
953 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);
957 color = particlepalette[20 + (rand()&7)];
958 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);
961 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
963 if (cl_particles_quake.integer)
966 color = particlepalette[230 + (rand()&7)];
967 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
968 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
972 color = particlepalette[226 + (rand()&7)];
973 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);
976 else if (effectnameindex == EFFECT_TR_VORESPIKE)
978 if (cl_particles_quake.integer)
980 color = particlepalette[152 + (rand()&3)];
981 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0);
983 else if (gamemode == GAME_GOODVSBAD2)
986 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);
988 else if (gamemode == GAME_PRYDON)
991 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);
994 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);
996 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
999 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);
1001 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1004 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);
1006 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1007 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);
1011 if (effectnameindex == EFFECT_TR_ROCKET)
1012 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);
1013 else if (effectnameindex == EFFECT_TR_GRENADE)
1014 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);
1016 // advance to next time and position
1019 VectorMA (pos, dec, dir, pos);
1022 ent->persistent.trail_time = len;
1024 else if (developer.integer >= 1)
1025 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1028 // this is also called on point effects with spawndlight = true and
1029 // spawnparticles = true
1030 // it is called CL_ParticleTrail because most code does not want to supply
1031 // these parameters, only trail handling does
1032 void CL_ParticleTrail(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, qboolean spawndlight, qboolean spawnparticles)
1035 qboolean found = false;
1036 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1037 return; // invalid effect index
1038 if (!particleeffectname[effectnameindex][0])
1039 return; // no such effect
1040 VectorLerp(originmins, 0.5, originmaxs, center);
1041 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1043 int effectinfoindex;
1046 particleeffectinfo_t *info;
1048 vec3_t centervelocity;
1054 qboolean underwater;
1055 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1056 VectorLerp(originmins, 0.5, originmaxs, center);
1057 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1058 supercontents = CL_PointSuperContents(center);
1059 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1060 VectorSubtract(originmaxs, originmins, traildir);
1061 traillen = VectorLength(traildir);
1062 VectorNormalize(traildir);
1063 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1065 if (info->effectnameindex == effectnameindex)
1068 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1070 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1073 // spawn a dlight if requested
1074 if (info->lightradiusstart > 0 && spawndlight)
1076 matrix4x4_t tempmatrix;
1077 if (info->trailspacing > 0)
1078 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1080 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1081 if (info->lighttime > 0 && info->lightradiusfade > 0)
1083 // light flash (explosion, etc)
1084 // called when effect starts
1085 CL_AllocLightFlash(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);
1090 // called by CL_LinkNetworkEntity
1091 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1092 R_RTLight_Update(&r_refdef.lights[r_refdef.numlights++], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1096 if (!spawnparticles)
1101 if (info->tex[1] > info->tex[0])
1103 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1104 tex = min(tex, info->tex[1] - 1);
1106 if (info->particletype == pt_decal)
1107 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]);
1108 else if (info->particletype == pt_beam)
1109 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);
1112 if (!cl_particles.integer)
1114 switch (info->particletype)
1116 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1117 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1118 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1119 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1120 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1121 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1124 VectorCopy(originmins, trailpos);
1125 if (info->trailspacing > 0)
1127 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1128 trailstep = info->trailspacing / cl_particles_quality.value;
1132 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1135 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1136 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1138 if (info->tex[1] > info->tex[0])
1140 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1141 tex = min(tex, info->tex[1] - 1);
1145 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1146 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1147 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1150 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);
1152 VectorMA(trailpos, trailstep, traildir, trailpos);
1159 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1162 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)
1164 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1172 void CL_EntityParticles (const entity_t *ent)
1175 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1176 static vec3_t avelocities[NUMVERTEXNORMALS];
1177 if (!cl_particles.integer) return;
1179 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1181 if (!avelocities[0][0])
1182 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1183 avelocities[0][i] = lhrandom(0, 2.55);
1185 for (i = 0;i < NUMVERTEXNORMALS;i++)
1187 yaw = cl.time * avelocities[i][0];
1188 pitch = cl.time * avelocities[i][1];
1189 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1190 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1191 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1192 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);
1197 void CL_ReadPointFile_f (void)
1199 vec3_t org, leakorg;
1201 char *pointfile = NULL, *pointfilepos, *t, tchar;
1202 char name[MAX_OSPATH];
1207 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1208 strlcat (name, ".pts", sizeof (name));
1209 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1212 Con_Printf("Could not open %s\n", name);
1216 Con_Printf("Reading %s...\n", name);
1217 VectorClear(leakorg);
1220 pointfilepos = pointfile;
1221 while (*pointfilepos)
1223 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1228 while (*t && *t != '\n' && *t != '\r')
1232 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1238 VectorCopy(org, leakorg);
1241 if (cl.num_particles < cl.max_particles - 3)
1244 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);
1247 Mem_Free(pointfile);
1248 VectorCopy(leakorg, org);
1249 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1251 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);
1252 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);
1253 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);
1258 CL_ParseParticleEffect
1260 Parse an effect out of the server message
1263 void CL_ParseParticleEffect (void)
1266 int i, count, msgcount, color;
1268 MSG_ReadVector(org, cls.protocol);
1269 for (i=0 ; i<3 ; i++)
1270 dir[i] = MSG_ReadChar ();
1271 msgcount = MSG_ReadByte ();
1272 color = MSG_ReadByte ();
1274 if (msgcount == 255)
1279 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1284 CL_ParticleExplosion
1288 void CL_ParticleExplosion (const vec3_t org)
1294 if (cl_stainmaps.integer)
1295 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1296 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1298 if (cl_particles_quake.integer)
1300 for (i = 0;i < 1024;i++)
1306 color = particlepalette[ramp1[r]];
1307 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256);
1311 color = particlepalette[ramp2[r]];
1312 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256);
1318 i = CL_PointSuperContents(org);
1319 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1321 if (cl_particles.integer && cl_particles_bubbles.integer)
1322 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1323 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);
1327 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1329 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1331 for (i = 0;i < 32;i++)
1335 for (k = 0;k < 16;k++)
1337 v[0] = org[0] + lhrandom(-48, 48);
1338 v[1] = org[1] + lhrandom(-48, 48);
1339 v[2] = org[2] + lhrandom(-48, 48);
1340 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1341 if (trace.fraction >= 0.1)
1344 VectorSubtract(trace.endpos, org, v2);
1345 VectorScale(v2, 2.0f, v2);
1346 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);
1350 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1352 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1356 for (k = 0;k < 16;k++)
1359 VectorMA(org, 128, v2, v);
1360 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1361 if (trace.fraction >= 0.1)
1364 VectorSubtract(trace.endpos, org, v2);
1365 VectorScale(v2, 2.0f, v2);
1366 particle(particletype + pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0);
1372 if (cl_particles_explosions_shell.integer)
1373 R_NewExplosion(org);
1378 CL_ParticleExplosion2
1382 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1385 if (!cl_particles.integer) return;
1387 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1389 k = particlepalette[colorStart + (i % colorLength)];
1390 if (cl_particles_quake.integer)
1391 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);
1393 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);
1397 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1399 if (cl_particles_sparks.integer)
1401 sparkcount *= cl_particles_quality.value;
1402 while(sparkcount-- > 0)
1403 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);
1407 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1409 if (cl_particles_smoke.integer)
1411 smokecount *= cl_particles_quality.value;
1412 while(smokecount-- > 0)
1413 particle(particletype + pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 4, 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, 0, 16);
1417 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)
1420 if (!cl_particles.integer) return;
1422 count = (int)(count * cl_particles_quality.value);
1425 k = particlepalette[colorbase + (rand()&3)];
1426 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);
1430 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1433 float z, minz, maxz;
1435 if (!cl_particles.integer) return;
1436 if (dir[2] < 0) // falling
1441 minz = z - fabs(dir[2]) * 0.1;
1442 maxz = z + fabs(dir[2]) * 0.1;
1443 minz = bound(mins[2], minz, maxs[2]);
1444 maxz = bound(mins[2], maxz, maxs[2]);
1446 count = (int)(count * cl_particles_quality.value);
1451 if (!cl_particles_rain.integer) break;
1452 count *= 4; // ick, this should be in the mod or maps?
1456 k = particlepalette[colorbase + (rand()&3)];
1457 if (gamemode == GAME_GOODVSBAD2)
1458 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);
1460 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);
1464 if (!cl_particles_snow.integer) break;
1467 k = particlepalette[colorbase + (rand()&3)];
1468 if (gamemode == GAME_GOODVSBAD2)
1469 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);
1471 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);
1473 VectorCopy(p->vel, p->relativedirection);
1477 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1486 void CL_MoveParticles (void)
1489 int i, maxparticle, j, a, content;
1490 float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
1491 particletype_t *decaltype, *bloodtype;
1495 // LordHavoc: early out condition
1496 if (!cl.num_particles)
1498 cl.free_particle = 0;
1502 frametime = bound(0, cl.time - cl.oldtime, 0.1);
1503 gravity = frametime * sv_gravity.value;
1504 dvel = 1+4*frametime;
1505 decalfade = frametime * 255 / cl_decals_fadetime.value;
1506 decaltype = particletype + pt_decal;
1507 bloodtype = particletype + pt_blood;
1511 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1515 if (cl.free_particle > i)
1516 cl.free_particle = i;
1521 // heavily optimized decal case
1522 if (p->type == decaltype)
1524 // FIXME: this has fairly wacky handling of alpha
1525 if (cl.time > p->time2 + cl_decals_time.value)
1527 p->alpha -= decalfade;
1531 if (cl.free_particle > i)
1532 cl.free_particle = i;
1538 if (cl.entities[p->owner].render.model == p->ownermodel)
1540 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1541 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1546 if (cl.free_particle > i)
1547 cl.free_particle = i;
1555 p->alpha -= p->alphafade * frametime;
1560 if (cl.free_particle > i)
1561 cl.free_particle = i;
1565 if (p->type->orientation != PARTICLE_BEAM)
1567 VectorCopy(p->org, oldorg);
1568 VectorMA(p->org, frametime, p->vel, p->org);
1569 VectorCopy(p->org, org);
1572 trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | (p->type == particletype + pt_rain ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
1573 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1574 // or if the trace hit something flagged as NOIMPACT
1575 // then remove the particle
1576 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1581 // react if the particle hit something
1582 if (trace.fraction < 1)
1584 VectorCopy(trace.endpos, p->org);
1585 if (p->type == particletype + pt_rain)
1587 // raindrop - splash on solid/water/slime/lava
1589 // convert from a raindrop particle to a rainsplash decal
1590 VectorCopy(trace.plane.normal, p->vel);
1591 VectorAdd(p->org, p->vel, p->org);
1592 p->type = particletype + pt_raindecal;
1593 p->texnum = tex_rainsplash;
1595 p->alphafade = p->alpha / 0.4;
1598 p->liquidfriction = 0;
1601 p->sizeincrease = p->size * 16;
1604 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, sv_gravity.value * 0.04 + p->vel[2]*16, 0, 0, 0, 32);
1606 else if (p->type == bloodtype)
1608 // blood - splash on solid
1609 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1614 if (cl_stainmaps.integer)
1615 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)));
1616 if (!cl_decals.integer)
1621 // convert from a blood particle to a blood decal
1622 VectorCopy(trace.plane.normal, p->vel);
1623 VectorAdd(p->org, p->vel, p->org);
1625 p->type = particletype + pt_decal;
1626 p->texnum = tex_blooddecal[rand()&7];
1628 p->ownermodel = cl.entities[hitent].render.model;
1629 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1630 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1631 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1636 p->liquidfriction = 0;
1640 else if (p->bounce < 0)
1642 // bounce -1 means remove on impact
1648 // anything else - bounce off solid
1649 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1650 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1651 if (DotProduct(p->vel, p->vel) < 0.03)
1652 VectorClear(p->vel);
1656 p->vel[2] -= p->gravity * gravity;
1658 if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1660 f = 1.0f - min(p->liquidfriction * frametime, 1);
1661 VectorScale(p->vel, f, p->vel);
1663 else if (p->airfriction)
1665 f = 1.0f - min(p->airfriction * frametime, 1);
1666 VectorScale(p->vel, f, p->vel);
1670 if (p->type != particletype + pt_static)
1672 switch (p->type - particletype)
1674 case pt_entityparticle:
1675 // particle that removes itself after one rendered frame
1682 a = CL_PointSuperContents(p->org);
1683 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1685 p->size += frametime * 8;
1686 //p->alpha -= bloodwaterfade;
1689 p->vel[2] -= gravity;
1690 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1694 a = CL_PointSuperContents(p->org);
1695 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1702 a = CL_PointSuperContents(p->org);
1703 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1707 if (cl.time > p->time2)
1710 p->time2 = cl.time + (rand() & 3) * 0.1;
1711 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1712 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1713 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1715 a = CL_PointSuperContents(p->org);
1716 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1724 cl.num_particles = maxparticle + 1;
1727 #define MAX_PARTICLETEXTURES 64
1728 // particletexture_t is a rectangle in the particlefonttexture
1729 typedef struct particletexture_s
1731 rtexture_t *texture;
1732 float s1, t1, s2, t2;
1736 static rtexturepool_t *particletexturepool;
1737 static rtexture_t *particlefonttexture;
1738 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1740 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1742 #define PARTICLETEXTURESIZE 64
1743 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1745 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1749 dz = 1 - (dx*dx+dy*dy);
1750 if (dz > 0) // it does hit the sphere
1754 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1755 VectorNormalize(normal);
1756 dot = DotProduct(normal, light);
1757 if (dot > 0.5) // interior reflection
1758 f += ((dot * 2) - 1);
1759 else if (dot < -0.5) // exterior reflection
1760 f += ((dot * -2) - 1);
1762 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1763 VectorNormalize(normal);
1764 dot = DotProduct(normal, light);
1765 if (dot > 0.5) // interior reflection
1766 f += ((dot * 2) - 1);
1767 else if (dot < -0.5) // exterior reflection
1768 f += ((dot * -2) - 1);
1770 f += 16; // just to give it a haze so you can see the outline
1771 f = bound(0, f, 255);
1772 return (unsigned char) f;
1778 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1780 int basex, basey, y;
1781 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1782 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1783 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1784 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1787 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1790 float cx, cy, dx, dy, f, iradius;
1792 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1793 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1794 iradius = 1.0f / radius;
1795 alpha *= (1.0f / 255.0f);
1796 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1798 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1802 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1805 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1806 d[0] += (int)(f * (red - d[0]));
1807 d[1] += (int)(f * (green - d[1]));
1808 d[2] += (int)(f * (blue - d[2]));
1814 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1817 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1819 data[0] = bound(minr, data[0], maxr);
1820 data[1] = bound(ming, data[1], maxg);
1821 data[2] = bound(minb, data[2], maxb);
1825 void particletextureinvert(unsigned char *data)
1828 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1830 data[0] = 255 - data[0];
1831 data[1] = 255 - data[1];
1832 data[2] = 255 - data[2];
1836 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1837 static void R_InitBloodTextures (unsigned char *particletexturedata)
1840 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1843 for (i = 0;i < 8;i++)
1845 memset(&data[0][0][0], 255, sizeof(data));
1846 for (k = 0;k < 24;k++)
1847 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1848 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1849 particletextureinvert(&data[0][0][0]);
1850 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1854 for (i = 0;i < 8;i++)
1856 memset(&data[0][0][0], 255, sizeof(data));
1858 for (j = 1;j < 10;j++)
1859 for (k = min(j, m - 1);k < m;k++)
1860 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1861 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1862 particletextureinvert(&data[0][0][0]);
1863 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1868 //uncomment this to make engine save out particle font to a tga file when run
1869 //#define DUMPPARTICLEFONT
1871 static void R_InitParticleTexture (void)
1873 int x, y, d, i, k, m;
1877 // a note: decals need to modulate (multiply) the background color to
1878 // properly darken it (stain), and they need to be able to alpha fade,
1879 // this is a very difficult challenge because it means fading to white
1880 // (no change to background) rather than black (darkening everything
1881 // behind the whole decal polygon), and to accomplish this the texture is
1882 // inverted (dark red blood on white background becomes brilliant cyan
1883 // and white on black background) so we can alpha fade it to black, then
1884 // we invert it again during the blendfunc to make it work...
1886 #ifndef DUMPPARTICLEFONT
1887 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1888 if (!particlefonttexture)
1891 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1892 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1893 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1896 for (i = 0;i < 8;i++)
1898 memset(&data[0][0][0], 255, sizeof(data));
1901 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1903 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1904 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1906 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1908 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1909 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1911 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1912 d = (noise2[y][x] - 128) * 3 + 192;
1914 d = (int)(d * (1-(dx*dx+dy*dy)));
1915 d = (d * noise1[y][x]) >> 7;
1916 d = bound(0, d, 255);
1917 data[y][x][3] = (unsigned char) d;
1924 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1928 memset(&data[0][0][0], 255, sizeof(data));
1929 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1931 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1932 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1934 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1935 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1936 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1939 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1942 memset(&data[0][0][0], 255, sizeof(data));
1943 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1945 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1946 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1948 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1949 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1950 d = bound(0, d, 255);
1951 data[y][x][3] = (unsigned char) d;
1954 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1957 memset(&data[0][0][0], 255, sizeof(data));
1958 light[0] = 1;light[1] = 1;light[2] = 1;
1959 VectorNormalize(light);
1960 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1962 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1963 // stretch upper half of bubble by +50% and shrink lower half by -50%
1964 // (this gives an elongated teardrop shape)
1966 dy = (dy - 0.5f) * 2.0f;
1968 dy = (dy - 0.5f) / 1.5f;
1969 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1971 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1972 // shrink bubble width to half
1974 data[y][x][3] = shadebubble(dx, dy, light);
1977 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1980 memset(&data[0][0][0], 255, sizeof(data));
1981 light[0] = 1;light[1] = 1;light[2] = 1;
1982 VectorNormalize(light);
1983 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1985 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1986 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1988 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1989 data[y][x][3] = shadebubble(dx, dy, light);
1992 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1994 // Blood particles and blood decals
1995 R_InitBloodTextures (particletexturedata);
1998 for (i = 0;i < 8;i++)
2000 memset(&data[0][0][0], 255, sizeof(data));
2001 for (k = 0;k < 12;k++)
2002 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2003 for (k = 0;k < 3;k++)
2004 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2005 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
2006 particletextureinvert(&data[0][0][0]);
2007 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
2010 #ifdef DUMPPARTICLEFONT
2011 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2014 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
2016 Mem_Free(particletexturedata);
2018 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2020 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
2021 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
2022 particletexture[i].texture = particlefonttexture;
2023 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
2024 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
2025 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2026 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2029 #ifndef DUMPPARTICLEFONT
2030 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
2031 if (!particletexture[tex_beam].texture)
2034 unsigned char noise3[64][64], data2[64][16][4];
2036 fractalnoise(&noise3[0][0], 64, 4);
2038 for (y = 0;y < 64;y++)
2040 dy = (y - 0.5f*64) / (64*0.5f-1);
2041 for (x = 0;x < 16;x++)
2043 dx = (x - 0.5f*16) / (16*0.5f-2);
2044 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2045 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2046 data2[y][x][3] = 255;
2050 #ifdef DUMPPARTICLEFONT
2051 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2053 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2055 particletexture[tex_beam].s1 = 0;
2056 particletexture[tex_beam].t1 = 0;
2057 particletexture[tex_beam].s2 = 1;
2058 particletexture[tex_beam].t2 = 1;
2061 static void r_part_start(void)
2063 particletexturepool = R_AllocTexturePool();
2064 R_InitParticleTexture ();
2065 CL_Particles_LoadEffectInfo();
2068 static void r_part_shutdown(void)
2070 R_FreeTexturePool(&particletexturepool);
2073 static void r_part_newmap(void)
2077 #define BATCHSIZE 256
2078 int particle_element3i[BATCHSIZE*6];
2079 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2081 void R_Particles_Init (void)
2084 for (i = 0;i < BATCHSIZE;i++)
2086 particle_element3i[i*6+0] = i*4+0;
2087 particle_element3i[i*6+1] = i*4+1;
2088 particle_element3i[i*6+2] = i*4+2;
2089 particle_element3i[i*6+3] = i*4+0;
2090 particle_element3i[i*6+4] = i*4+2;
2091 particle_element3i[i*6+5] = i*4+3;
2094 Cvar_RegisterVariable(&r_drawparticles);
2095 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2098 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2100 int surfacelistindex;
2101 int batchstart, batchcount;
2102 const particle_t *p;
2104 rtexture_t *texture;
2105 float *v3f, *t2f, *c4f;
2107 R_Mesh_Matrix(&identitymatrix);
2108 R_Mesh_ResetTextureState();
2109 R_Mesh_VertexPointer(particle_vertex3f);
2110 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f);
2111 R_Mesh_ColorPointer(particle_color4f);
2112 GL_DepthMask(false);
2114 GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
2116 // first generate all the vertices at once
2117 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2119 particletexture_t *tex;
2121 float up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
2123 p = cl.particles + surfacelist[surfacelistindex];
2125 blendmode = p->type->blendmode;
2127 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2128 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2129 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2130 ca = p->alpha * (1.0f / 255.0f);
2131 if (blendmode == PBLEND_MOD)
2141 ca /= cl_particles_quality.value;
2142 if (p->type->lighting)
2144 float ambient[3], diffuse[3], diffusenormal[3];
2145 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2146 cr *= (ambient[0] + 0.5 * diffuse[0]);
2147 cg *= (ambient[1] + 0.5 * diffuse[1]);
2148 cb *= (ambient[2] + 0.5 * diffuse[2]);
2150 if (r_refdef.fogenabled)
2152 fog = VERTEXFOGTABLE(VectorDistance(p->org, r_view.origin));
2157 if (blendmode == PBLEND_ALPHA)
2159 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2160 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2161 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2164 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2165 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2166 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2167 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2169 size = p->size * cl_particles_size.value;
2171 tex = &particletexture[p->texnum];
2172 if (p->type->orientation == PARTICLE_BILLBOARD)
2174 VectorScale(r_view.left, -size, right);
2175 VectorScale(r_view.up, size, up);
2176 v3f[ 0] = org[0] - right[0] - up[0];
2177 v3f[ 1] = org[1] - right[1] - up[1];
2178 v3f[ 2] = org[2] - right[2] - up[2];
2179 v3f[ 3] = org[0] - right[0] + up[0];
2180 v3f[ 4] = org[1] - right[1] + up[1];
2181 v3f[ 5] = org[2] - right[2] + up[2];
2182 v3f[ 6] = org[0] + right[0] + up[0];
2183 v3f[ 7] = org[1] + right[1] + up[1];
2184 v3f[ 8] = org[2] + right[2] + up[2];
2185 v3f[ 9] = org[0] + right[0] - up[0];
2186 v3f[10] = org[1] + right[1] - up[1];
2187 v3f[11] = org[2] + right[2] - up[2];
2188 t2f[0] = tex->s1;t2f[1] = tex->t2;
2189 t2f[2] = tex->s1;t2f[3] = tex->t1;
2190 t2f[4] = tex->s2;t2f[5] = tex->t1;
2191 t2f[6] = tex->s2;t2f[7] = tex->t2;
2193 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2196 if (DotProduct(p->vel, r_view.origin) > DotProduct(p->vel, org))
2198 VectorNegate(p->vel, v);
2199 VectorVectors(v, right, up);
2202 VectorVectors(p->vel, right, up);
2203 VectorScale(right, size, right);
2204 VectorScale(up, size, up);
2205 v3f[ 0] = org[0] - right[0] - up[0];
2206 v3f[ 1] = org[1] - right[1] - up[1];
2207 v3f[ 2] = org[2] - right[2] - up[2];
2208 v3f[ 3] = org[0] - right[0] + up[0];
2209 v3f[ 4] = org[1] - right[1] + up[1];
2210 v3f[ 5] = org[2] - right[2] + up[2];
2211 v3f[ 6] = org[0] + right[0] + up[0];
2212 v3f[ 7] = org[1] + right[1] + up[1];
2213 v3f[ 8] = org[2] + right[2] + up[2];
2214 v3f[ 9] = org[0] + right[0] - up[0];
2215 v3f[10] = org[1] + right[1] - up[1];
2216 v3f[11] = org[2] + right[2] - up[2];
2217 t2f[0] = tex->s1;t2f[1] = tex->t2;
2218 t2f[2] = tex->s1;t2f[3] = tex->t1;
2219 t2f[4] = tex->s2;t2f[5] = tex->t1;
2220 t2f[6] = tex->s2;t2f[7] = tex->t2;
2222 else if (p->type->orientation == PARTICLE_SPARK)
2224 VectorMA(org, -0.02, p->vel, v);
2225 VectorMA(org, 0.02, p->vel, up2);
2226 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2227 t2f[0] = tex->s1;t2f[1] = tex->t2;
2228 t2f[2] = tex->s1;t2f[3] = tex->t1;
2229 t2f[4] = tex->s2;t2f[5] = tex->t1;
2230 t2f[6] = tex->s2;t2f[7] = tex->t2;
2232 else if (p->type->orientation == PARTICLE_BEAM)
2234 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2235 VectorSubtract(p->vel, org, up);
2236 VectorNormalize(up);
2237 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2238 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2239 t2f[0] = 1;t2f[1] = v[0];
2240 t2f[2] = 0;t2f[3] = v[0];
2241 t2f[4] = 0;t2f[5] = v[1];
2242 t2f[6] = 1;t2f[7] = v[1];
2246 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2251 // now render batches of particles based on blendmode and texture
2252 blendmode = PBLEND_ADD;
2253 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2254 texture = particletexture[63].texture;
2255 R_Mesh_TexBind(0, R_GetTexture(texture));
2256 GL_LockArrays(0, numsurfaces*4);
2259 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2261 p = cl.particles + surfacelist[surfacelistindex];
2263 if (blendmode != p->type->blendmode)
2266 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2268 batchstart = surfacelistindex;
2269 blendmode = p->type->blendmode;
2270 if (blendmode == PBLEND_ALPHA)
2271 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2272 else if (blendmode == PBLEND_ADD)
2273 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2274 else //if (blendmode == PBLEND_MOD)
2275 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2277 if (texture != particletexture[p->texnum].texture)
2280 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2282 batchstart = surfacelistindex;
2283 texture = particletexture[p->texnum].texture;
2284 R_Mesh_TexBind(0, R_GetTexture(texture));
2290 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2291 GL_LockArrays(0, 0);
2294 void R_DrawParticles (void)
2297 float minparticledist;
2300 // LordHavoc: early out conditions
2301 if ((!cl.num_particles) || (!r_drawparticles.integer))
2304 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2306 // LordHavoc: only render if not too close
2307 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2311 r_refdef.stats.particles++;
2312 if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2313 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);