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, float smokecount);
544 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)
547 matrix4x4_t tempmatrix;
548 VectorLerp(originmins, 0.5, originmaxs, center);
549 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
550 if (effectnameindex == EFFECT_SVC_PARTICLE)
552 if (cl_particles.integer)
554 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
556 CL_ParticleExplosion(center);
557 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
558 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
561 count *= cl_particles_quality.value;
562 for (;count > 0;count--)
564 int k = particlepalette[palettecolor + (rand()&7)];
565 if (cl_particles_quake.integer)
566 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);
567 else if (gamemode == GAME_GOODVSBAD2)
568 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);
570 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);
575 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
576 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
577 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
578 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
579 else if (effectnameindex == EFFECT_TE_SPIKE)
581 if (cl_particles_bulletimpacts.integer)
583 if (cl_particles_quake.integer)
585 if (cl_particles_smoke.integer)
586 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
589 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
592 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
593 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
595 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
597 if (cl_particles_bulletimpacts.integer)
599 if (cl_particles_quake.integer)
601 if (cl_particles_smoke.integer)
602 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
605 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
608 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
609 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
610 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);
612 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
614 if (cl_particles_bulletimpacts.integer)
616 if (cl_particles_quake.integer)
618 if (cl_particles_smoke.integer)
619 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
622 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
625 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
626 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
628 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
630 if (cl_particles_bulletimpacts.integer)
632 if (cl_particles_quake.integer)
634 if (cl_particles_smoke.integer)
635 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
638 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
641 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
642 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
643 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);
645 else if (effectnameindex == EFFECT_TE_BLOOD)
647 if (!cl_particles_blood.integer)
649 if (cl_particles_quake.integer)
650 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
653 static double bloodaccumulator = 0;
654 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
655 for (;bloodaccumulator > 0;bloodaccumulator--)
656 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);
659 else if (effectnameindex == EFFECT_TE_SPARK)
660 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count, 0);
661 else if (effectnameindex == EFFECT_TE_PLASMABURN)
663 // plasma scorch mark
664 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
665 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
666 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
668 else if (effectnameindex == EFFECT_TE_GUNSHOT)
670 if (cl_particles_bulletimpacts.integer)
672 if (cl_particles_quake.integer)
673 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
675 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
678 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
679 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
681 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
683 if (cl_particles_bulletimpacts.integer)
685 if (cl_particles_quake.integer)
686 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
688 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
691 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
692 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
693 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);
695 else if (effectnameindex == EFFECT_TE_EXPLOSION)
697 CL_ParticleExplosion(center);
698 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);
700 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
702 CL_ParticleExplosion(center);
703 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);
705 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
707 if (cl_particles_quake.integer)
710 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
713 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);
715 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);
719 CL_ParticleExplosion(center);
720 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);
722 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
723 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);
724 else if (effectnameindex == EFFECT_TE_FLAMEJET)
726 count *= cl_particles_quality.value;
728 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);
730 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
732 float i, j, inc, vel;
735 inc = 8 / cl_particles_quality.value;
736 for (i = -128;i < 128;i += inc)
738 for (j = -128;j < 128;j += inc)
740 dir[0] = j + lhrandom(0, inc);
741 dir[1] = i + lhrandom(0, inc);
743 org[0] = center[0] + dir[0];
744 org[1] = center[1] + dir[1];
745 org[2] = center[2] + lhrandom(0, 64);
746 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
747 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);
751 else if (effectnameindex == EFFECT_TE_TELEPORT)
753 float i, j, k, inc, vel;
756 inc = 8 / cl_particles_quality.value;
757 for (i = -16;i < 16;i += inc)
759 for (j = -16;j < 16;j += inc)
761 for (k = -24;k < 32;k += inc)
763 VectorSet(dir, i*8, j*8, k*8);
764 VectorNormalize(dir);
765 vel = lhrandom(50, 113);
766 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);
770 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);
771 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);
773 else if (effectnameindex == EFFECT_TE_TEI_G3)
774 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);
775 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
777 if (cl_particles_smoke.integer)
779 count *= 0.25f * cl_particles_quality.value;
781 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);
784 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
786 CL_ParticleExplosion(center);
787 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);
789 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
792 if (cl_stainmaps.integer)
793 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
794 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
795 if (cl_particles_smoke.integer)
796 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
797 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);
798 if (cl_particles_sparks.integer)
799 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
800 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);
801 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);
803 else if (effectnameindex == EFFECT_EF_FLAME)
805 count *= 300 * cl_particles_quality.value;
807 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);
808 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);
810 else if (effectnameindex == EFFECT_EF_STARDUST)
812 count *= 200 * cl_particles_quality.value;
814 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);
815 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);
817 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
821 int smoke, blood, bubbles, r, color;
823 if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
826 Vector4Set(light, 0, 0, 0, 0);
828 if (effectnameindex == EFFECT_TR_ROCKET)
829 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
830 else if (effectnameindex == EFFECT_TR_VORESPIKE)
832 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
833 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
835 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
837 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
838 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
842 matrix4x4_t tempmatrix;
843 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
844 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);
851 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
854 VectorSubtract(originmaxs, originmins, dir);
855 len = VectorNormalizeLength(dir);
858 dec = -ent->persistent.trail_time;
859 ent->persistent.trail_time += len;
860 if (ent->persistent.trail_time < 0.01f)
863 // if we skip out, leave it reset
864 ent->persistent.trail_time = 0.0f;
869 // advance into this frame to reach the first puff location
870 VectorMA(originmins, dec, dir, pos);
873 smoke = cl_particles.integer && cl_particles_smoke.integer;
874 blood = cl_particles.integer && cl_particles_blood.integer;
875 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
876 qd = 1.0f / cl_particles_quality.value;
883 if (effectnameindex == EFFECT_TR_BLOOD)
885 if (cl_particles_quake.integer)
887 color = particlepalette[67 + (rand()&3)];
888 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);
893 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);
896 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
898 if (cl_particles_quake.integer)
901 color = particlepalette[67 + (rand()&3)];
902 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);
907 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);
913 if (effectnameindex == EFFECT_TR_ROCKET)
915 if (cl_particles_quake.integer)
918 color = particlepalette[ramp3[r]];
919 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);
923 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);
924 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);
927 else if (effectnameindex == EFFECT_TR_GRENADE)
929 if (cl_particles_quake.integer)
932 color = particlepalette[ramp3[r]];
933 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);
937 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);
940 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
942 if (cl_particles_quake.integer)
945 color = particlepalette[52 + (rand()&7)];
946 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);
947 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);
949 else if (gamemode == GAME_GOODVSBAD2)
952 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);
956 color = particlepalette[20 + (rand()&7)];
957 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);
960 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
962 if (cl_particles_quake.integer)
965 color = particlepalette[230 + (rand()&7)];
966 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);
967 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);
971 color = particlepalette[226 + (rand()&7)];
972 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);
975 else if (effectnameindex == EFFECT_TR_VORESPIKE)
977 if (cl_particles_quake.integer)
979 color = particlepalette[152 + (rand()&3)];
980 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);
982 else if (gamemode == GAME_GOODVSBAD2)
985 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);
987 else if (gamemode == GAME_PRYDON)
990 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);
993 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);
995 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
998 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);
1000 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1003 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);
1005 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1006 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);
1010 if (effectnameindex == EFFECT_TR_ROCKET)
1011 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);
1012 else if (effectnameindex == EFFECT_TR_GRENADE)
1013 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);
1015 // advance to next time and position
1018 VectorMA (pos, dec, dir, pos);
1021 ent->persistent.trail_time = len;
1023 else if (developer.integer >= 1)
1024 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1027 // this is also called on point effects with spawndlight = true and
1028 // spawnparticles = true
1029 // it is called CL_ParticleTrail because most code does not want to supply
1030 // these parameters, only trail handling does
1031 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)
1034 qboolean found = false;
1035 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1036 return; // invalid effect index
1037 if (!particleeffectname[effectnameindex][0])
1038 return; // no such effect
1039 VectorLerp(originmins, 0.5, originmaxs, center);
1040 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1042 int effectinfoindex;
1045 particleeffectinfo_t *info;
1047 vec3_t centervelocity;
1053 qboolean underwater;
1054 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1055 VectorLerp(originmins, 0.5, originmaxs, center);
1056 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1057 supercontents = CL_PointSuperContents(center);
1058 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1059 VectorSubtract(originmaxs, originmins, traildir);
1060 traillen = VectorLength(traildir);
1061 VectorNormalize(traildir);
1062 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1064 if (info->effectnameindex == effectnameindex)
1067 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1069 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1072 // spawn a dlight if requested
1073 if (info->lightradiusstart > 0 && spawndlight)
1075 matrix4x4_t tempmatrix;
1076 if (info->trailspacing > 0)
1077 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1079 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1080 if (info->lighttime > 0 && info->lightradiusfade > 0)
1082 // light flash (explosion, etc)
1083 // called when effect starts
1084 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);
1089 // called by CL_LinkNetworkEntity
1090 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1091 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);
1095 if (!spawnparticles)
1100 if (info->tex[1] > info->tex[0])
1102 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1103 tex = min(tex, info->tex[1] - 1);
1105 if (info->particletype == pt_decal)
1106 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]);
1107 else if (info->particletype == pt_beam)
1108 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);
1111 if (!cl_particles.integer)
1113 switch (info->particletype)
1115 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1116 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1117 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1118 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1119 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1120 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1123 VectorCopy(originmins, trailpos);
1124 if (info->trailspacing > 0)
1126 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1127 trailstep = info->trailspacing / cl_particles_quality.value;
1131 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1134 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1135 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1137 if (info->tex[1] > info->tex[0])
1139 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1140 tex = min(tex, info->tex[1] - 1);
1144 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1145 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1146 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1149 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);
1151 VectorMA(trailpos, trailstep, traildir, trailpos);
1158 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1161 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)
1163 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1171 void CL_EntityParticles (const entity_t *ent)
1174 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1175 static vec3_t avelocities[NUMVERTEXNORMALS];
1176 if (!cl_particles.integer) return;
1178 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1180 if (!avelocities[0][0])
1181 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1182 avelocities[0][i] = lhrandom(0, 2.55);
1184 for (i = 0;i < NUMVERTEXNORMALS;i++)
1186 yaw = cl.time * avelocities[i][0];
1187 pitch = cl.time * avelocities[i][1];
1188 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1189 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1190 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1191 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);
1196 void CL_ReadPointFile_f (void)
1198 vec3_t org, leakorg;
1200 char *pointfile = NULL, *pointfilepos, *t, tchar;
1201 char name[MAX_OSPATH];
1206 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1207 strlcat (name, ".pts", sizeof (name));
1208 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1211 Con_Printf("Could not open %s\n", name);
1215 Con_Printf("Reading %s...\n", name);
1216 VectorClear(leakorg);
1219 pointfilepos = pointfile;
1220 while (*pointfilepos)
1222 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1227 while (*t && *t != '\n' && *t != '\r')
1231 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1237 VectorCopy(org, leakorg);
1240 if (cl.num_particles < cl.max_particles - 3)
1243 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);
1246 Mem_Free(pointfile);
1247 VectorCopy(leakorg, org);
1248 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1250 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);
1251 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);
1252 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);
1257 CL_ParseParticleEffect
1259 Parse an effect out of the server message
1262 void CL_ParseParticleEffect (void)
1265 int i, count, msgcount, color;
1267 MSG_ReadVector(org, cls.protocol);
1268 for (i=0 ; i<3 ; i++)
1269 dir[i] = MSG_ReadChar ();
1270 msgcount = MSG_ReadByte ();
1271 color = MSG_ReadByte ();
1273 if (msgcount == 255)
1278 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1283 CL_ParticleExplosion
1287 void CL_ParticleExplosion (const vec3_t org)
1293 if (cl_stainmaps.integer)
1294 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1295 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1297 if (cl_particles_quake.integer)
1299 for (i = 0;i < 1024;i++)
1305 color = particlepalette[ramp1[r]];
1306 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);
1310 color = particlepalette[ramp2[r]];
1311 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);
1317 i = CL_PointSuperContents(org);
1318 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1320 if (cl_particles.integer && cl_particles_bubbles.integer)
1321 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1322 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);
1326 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1328 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1330 for (i = 0;i < 32;i++)
1334 for (k = 0;k < 16;k++)
1336 v[0] = org[0] + lhrandom(-48, 48);
1337 v[1] = org[1] + lhrandom(-48, 48);
1338 v[2] = org[2] + lhrandom(-48, 48);
1339 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1340 if (trace.fraction >= 0.1)
1343 VectorSubtract(trace.endpos, org, v2);
1344 VectorScale(v2, 2.0f, v2);
1345 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);
1349 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1350 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1351 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);
1355 if (cl_particles_explosions_shell.integer)
1356 R_NewExplosion(org);
1361 CL_ParticleExplosion2
1365 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1368 if (!cl_particles.integer) return;
1370 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1372 k = particlepalette[colorStart + (i % colorLength)];
1373 if (cl_particles_quake.integer)
1374 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);
1376 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);
1380 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1382 if (cl_particles_sparks.integer)
1384 sparkcount *= cl_particles_quality.value;
1385 while(sparkcount-- > 0)
1386 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);
1388 if (cl_particles_smoke.integer)
1390 smokecount *= cl_particles_quality.value;
1391 while(smokecount-- > 0)
1392 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);
1396 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)
1399 if (!cl_particles.integer) return;
1401 count = (int)(count * cl_particles_quality.value);
1404 k = particlepalette[colorbase + (rand()&3)];
1405 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);
1409 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1412 float z, minz, maxz;
1414 if (!cl_particles.integer) return;
1415 if (dir[2] < 0) // falling
1420 minz = z - fabs(dir[2]) * 0.1;
1421 maxz = z + fabs(dir[2]) * 0.1;
1422 minz = bound(mins[2], minz, maxs[2]);
1423 maxz = bound(mins[2], maxz, maxs[2]);
1425 count = (int)(count * cl_particles_quality.value);
1430 if (!cl_particles_rain.integer) break;
1431 count *= 4; // ick, this should be in the mod or maps?
1435 k = particlepalette[colorbase + (rand()&3)];
1436 if (gamemode == GAME_GOODVSBAD2)
1437 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);
1439 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);
1443 if (!cl_particles_snow.integer) break;
1446 k = particlepalette[colorbase + (rand()&3)];
1447 if (gamemode == GAME_GOODVSBAD2)
1448 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);
1450 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);
1452 VectorCopy(p->vel, p->relativedirection);
1456 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1465 void CL_MoveParticles (void)
1468 int i, maxparticle, j, a, content;
1469 float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
1470 particletype_t *decaltype, *bloodtype;
1474 // LordHavoc: early out condition
1475 if (!cl.num_particles)
1477 cl.free_particle = 0;
1481 frametime = bound(0, cl.time - cl.oldtime, 0.1);
1482 gravity = frametime * sv_gravity.value;
1483 dvel = 1+4*frametime;
1484 decalfade = frametime * 255 / cl_decals_fadetime.value;
1485 decaltype = particletype + pt_decal;
1486 bloodtype = particletype + pt_blood;
1490 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1494 if (cl.free_particle > i)
1495 cl.free_particle = i;
1500 // heavily optimized decal case
1501 if (p->type == decaltype)
1503 // FIXME: this has fairly wacky handling of alpha
1504 if (cl.time > p->time2 + cl_decals_time.value)
1506 p->alpha -= decalfade;
1510 if (cl.free_particle > i)
1511 cl.free_particle = i;
1517 if (cl.entities[p->owner].render.model == p->ownermodel)
1519 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1520 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1525 if (cl.free_particle > i)
1526 cl.free_particle = i;
1534 p->alpha -= p->alphafade * frametime;
1539 if (cl.free_particle > i)
1540 cl.free_particle = i;
1544 if (p->type->orientation != PARTICLE_BEAM)
1546 VectorCopy(p->org, oldorg);
1547 VectorMA(p->org, frametime, p->vel, p->org);
1548 VectorCopy(p->org, org);
1551 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);
1552 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1553 // or if the trace hit something flagged as NOIMPACT
1554 // then remove the particle
1555 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1560 // react if the particle hit something
1561 if (trace.fraction < 1)
1563 VectorCopy(trace.endpos, p->org);
1564 if (p->type == particletype + pt_rain)
1566 // raindrop - splash on solid/water/slime/lava
1568 // convert from a raindrop particle to a rainsplash decal
1569 VectorCopy(trace.plane.normal, p->vel);
1570 VectorAdd(p->org, p->vel, p->org);
1571 p->type = particletype + pt_raindecal;
1572 p->texnum = tex_rainsplash;
1574 p->alphafade = p->alpha / 0.4;
1577 p->liquidfriction = 0;
1580 p->sizeincrease = p->size * 16;
1583 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);
1585 else if (p->type == bloodtype)
1587 // blood - splash on solid
1588 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1593 if (cl_stainmaps.integer)
1594 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)));
1595 if (!cl_decals.integer)
1600 // convert from a blood particle to a blood decal
1601 VectorCopy(trace.plane.normal, p->vel);
1602 VectorAdd(p->org, p->vel, p->org);
1604 p->type = particletype + pt_decal;
1605 p->texnum = tex_blooddecal[rand()&7];
1607 p->ownermodel = cl.entities[hitent].render.model;
1608 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1609 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1610 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1615 p->liquidfriction = 0;
1619 else if (p->bounce < 0)
1621 // bounce -1 means remove on impact
1627 // anything else - bounce off solid
1628 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1629 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1630 if (DotProduct(p->vel, p->vel) < 0.03)
1631 VectorClear(p->vel);
1635 p->vel[2] -= p->gravity * gravity;
1637 if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1639 f = 1.0f - min(p->liquidfriction * frametime, 1);
1640 VectorScale(p->vel, f, p->vel);
1642 else if (p->airfriction)
1644 f = 1.0f - min(p->airfriction * frametime, 1);
1645 VectorScale(p->vel, f, p->vel);
1649 if (p->type != particletype + pt_static)
1651 switch (p->type - particletype)
1653 case pt_entityparticle:
1654 // particle that removes itself after one rendered frame
1661 a = CL_PointSuperContents(p->org);
1662 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1664 p->size += frametime * 8;
1665 //p->alpha -= bloodwaterfade;
1668 p->vel[2] -= gravity;
1669 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1673 a = CL_PointSuperContents(p->org);
1674 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1681 a = CL_PointSuperContents(p->org);
1682 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1686 if (cl.time > p->time2)
1689 p->time2 = cl.time + (rand() & 3) * 0.1;
1690 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1691 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1692 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1694 a = CL_PointSuperContents(p->org);
1695 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1703 cl.num_particles = maxparticle + 1;
1706 #define MAX_PARTICLETEXTURES 64
1707 // particletexture_t is a rectangle in the particlefonttexture
1708 typedef struct particletexture_s
1710 rtexture_t *texture;
1711 float s1, t1, s2, t2;
1715 static rtexturepool_t *particletexturepool;
1716 static rtexture_t *particlefonttexture;
1717 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1719 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1721 #define PARTICLETEXTURESIZE 64
1722 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1724 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1728 dz = 1 - (dx*dx+dy*dy);
1729 if (dz > 0) // it does hit the sphere
1733 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1734 VectorNormalize(normal);
1735 dot = DotProduct(normal, light);
1736 if (dot > 0.5) // interior reflection
1737 f += ((dot * 2) - 1);
1738 else if (dot < -0.5) // exterior reflection
1739 f += ((dot * -2) - 1);
1741 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1742 VectorNormalize(normal);
1743 dot = DotProduct(normal, light);
1744 if (dot > 0.5) // interior reflection
1745 f += ((dot * 2) - 1);
1746 else if (dot < -0.5) // exterior reflection
1747 f += ((dot * -2) - 1);
1749 f += 16; // just to give it a haze so you can see the outline
1750 f = bound(0, f, 255);
1751 return (unsigned char) f;
1757 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1759 int basex, basey, y;
1760 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1761 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1762 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1763 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1766 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1769 float cx, cy, dx, dy, f, iradius;
1771 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1772 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1773 iradius = 1.0f / radius;
1774 alpha *= (1.0f / 255.0f);
1775 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1777 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1781 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1784 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1785 d[0] += (int)(f * (red - d[0]));
1786 d[1] += (int)(f * (green - d[1]));
1787 d[2] += (int)(f * (blue - d[2]));
1793 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1796 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1798 data[0] = bound(minr, data[0], maxr);
1799 data[1] = bound(ming, data[1], maxg);
1800 data[2] = bound(minb, data[2], maxb);
1804 void particletextureinvert(unsigned char *data)
1807 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1809 data[0] = 255 - data[0];
1810 data[1] = 255 - data[1];
1811 data[2] = 255 - data[2];
1815 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1816 static void R_InitBloodTextures (unsigned char *particletexturedata)
1819 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1822 for (i = 0;i < 8;i++)
1824 memset(&data[0][0][0], 255, sizeof(data));
1825 for (k = 0;k < 24;k++)
1826 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1827 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1828 particletextureinvert(&data[0][0][0]);
1829 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1833 for (i = 0;i < 8;i++)
1835 memset(&data[0][0][0], 255, sizeof(data));
1837 for (j = 1;j < 10;j++)
1838 for (k = min(j, m - 1);k < m;k++)
1839 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1840 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1841 particletextureinvert(&data[0][0][0]);
1842 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1847 //uncomment this to make engine save out particle font to a tga file when run
1848 //#define DUMPPARTICLEFONT
1850 static void R_InitParticleTexture (void)
1852 int x, y, d, i, k, m;
1856 // a note: decals need to modulate (multiply) the background color to
1857 // properly darken it (stain), and they need to be able to alpha fade,
1858 // this is a very difficult challenge because it means fading to white
1859 // (no change to background) rather than black (darkening everything
1860 // behind the whole decal polygon), and to accomplish this the texture is
1861 // inverted (dark red blood on white background becomes brilliant cyan
1862 // and white on black background) so we can alpha fade it to black, then
1863 // we invert it again during the blendfunc to make it work...
1865 #ifndef DUMPPARTICLEFONT
1866 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1867 if (!particlefonttexture)
1870 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1871 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1872 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1875 for (i = 0;i < 8;i++)
1877 memset(&data[0][0][0], 255, sizeof(data));
1880 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1882 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1883 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1885 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1887 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1888 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1890 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1891 d = (noise2[y][x] - 128) * 3 + 192;
1893 d = (int)(d * (1-(dx*dx+dy*dy)));
1894 d = (d * noise1[y][x]) >> 7;
1895 d = bound(0, d, 255);
1896 data[y][x][3] = (unsigned char) d;
1903 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1907 memset(&data[0][0][0], 255, sizeof(data));
1908 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1910 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1911 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1913 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1914 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1915 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1918 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1921 memset(&data[0][0][0], 255, sizeof(data));
1922 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1924 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1925 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1927 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1928 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1929 d = bound(0, d, 255);
1930 data[y][x][3] = (unsigned char) d;
1933 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1936 memset(&data[0][0][0], 255, sizeof(data));
1937 light[0] = 1;light[1] = 1;light[2] = 1;
1938 VectorNormalize(light);
1939 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1941 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1942 // stretch upper half of bubble by +50% and shrink lower half by -50%
1943 // (this gives an elongated teardrop shape)
1945 dy = (dy - 0.5f) * 2.0f;
1947 dy = (dy - 0.5f) / 1.5f;
1948 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1950 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1951 // shrink bubble width to half
1953 data[y][x][3] = shadebubble(dx, dy, light);
1956 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1959 memset(&data[0][0][0], 255, sizeof(data));
1960 light[0] = 1;light[1] = 1;light[2] = 1;
1961 VectorNormalize(light);
1962 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1964 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1965 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1967 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1968 data[y][x][3] = shadebubble(dx, dy, light);
1971 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1973 // Blood particles and blood decals
1974 R_InitBloodTextures (particletexturedata);
1977 for (i = 0;i < 8;i++)
1979 memset(&data[0][0][0], 255, sizeof(data));
1980 for (k = 0;k < 12;k++)
1981 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1982 for (k = 0;k < 3;k++)
1983 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1984 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1985 particletextureinvert(&data[0][0][0]);
1986 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1989 #ifdef DUMPPARTICLEFONT
1990 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1993 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1995 Mem_Free(particletexturedata);
1997 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1999 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
2000 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
2001 particletexture[i].texture = particlefonttexture;
2002 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
2003 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
2004 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2005 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2008 #ifndef DUMPPARTICLEFONT
2009 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
2010 if (!particletexture[tex_beam].texture)
2013 unsigned char noise3[64][64], data2[64][16][4];
2015 fractalnoise(&noise3[0][0], 64, 4);
2017 for (y = 0;y < 64;y++)
2019 dy = (y - 0.5f*64) / (64*0.5f-1);
2020 for (x = 0;x < 16;x++)
2022 dx = (x - 0.5f*16) / (16*0.5f-2);
2023 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2024 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2025 data2[y][x][3] = 255;
2029 #ifdef DUMPPARTICLEFONT
2030 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2032 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2034 particletexture[tex_beam].s1 = 0;
2035 particletexture[tex_beam].t1 = 0;
2036 particletexture[tex_beam].s2 = 1;
2037 particletexture[tex_beam].t2 = 1;
2040 static void r_part_start(void)
2042 particletexturepool = R_AllocTexturePool();
2043 R_InitParticleTexture ();
2044 CL_Particles_LoadEffectInfo();
2047 static void r_part_shutdown(void)
2049 R_FreeTexturePool(&particletexturepool);
2052 static void r_part_newmap(void)
2056 #define BATCHSIZE 256
2057 int particle_element3i[BATCHSIZE*6];
2058 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2060 void R_Particles_Init (void)
2063 for (i = 0;i < BATCHSIZE;i++)
2065 particle_element3i[i*6+0] = i*4+0;
2066 particle_element3i[i*6+1] = i*4+1;
2067 particle_element3i[i*6+2] = i*4+2;
2068 particle_element3i[i*6+3] = i*4+0;
2069 particle_element3i[i*6+4] = i*4+2;
2070 particle_element3i[i*6+5] = i*4+3;
2073 Cvar_RegisterVariable(&r_drawparticles);
2074 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2077 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2079 int surfacelistindex;
2080 int batchstart, batchcount;
2081 const particle_t *p;
2083 rtexture_t *texture;
2084 float *v3f, *t2f, *c4f;
2086 R_Mesh_Matrix(&identitymatrix);
2087 R_Mesh_ResetTextureState();
2088 R_Mesh_VertexPointer(particle_vertex3f);
2089 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f);
2090 R_Mesh_ColorPointer(particle_color4f);
2091 GL_DepthMask(false);
2093 GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
2095 // first generate all the vertices at once
2096 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2098 particletexture_t *tex;
2100 float up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
2102 p = cl.particles + surfacelist[surfacelistindex];
2104 blendmode = p->type->blendmode;
2106 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2107 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2108 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2109 ca = p->alpha * (1.0f / 255.0f);
2110 if (blendmode == PBLEND_MOD)
2120 ca /= cl_particles_quality.value;
2121 if (p->type->lighting)
2123 float ambient[3], diffuse[3], diffusenormal[3];
2124 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2125 cr *= (ambient[0] + 0.5 * diffuse[0]);
2126 cg *= (ambient[1] + 0.5 * diffuse[1]);
2127 cb *= (ambient[2] + 0.5 * diffuse[2]);
2129 if (r_refdef.fogenabled)
2131 fog = VERTEXFOGTABLE(VectorDistance(p->org, r_view.origin));
2136 if (blendmode == PBLEND_ALPHA)
2138 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2139 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2140 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2143 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2144 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2145 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2146 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2148 size = p->size * cl_particles_size.value;
2150 tex = &particletexture[p->texnum];
2151 if (p->type->orientation == PARTICLE_BILLBOARD)
2153 VectorScale(r_view.left, -size, right);
2154 VectorScale(r_view.up, size, up);
2155 v3f[ 0] = org[0] - right[0] - up[0];
2156 v3f[ 1] = org[1] - right[1] - up[1];
2157 v3f[ 2] = org[2] - right[2] - up[2];
2158 v3f[ 3] = org[0] - right[0] + up[0];
2159 v3f[ 4] = org[1] - right[1] + up[1];
2160 v3f[ 5] = org[2] - right[2] + up[2];
2161 v3f[ 6] = org[0] + right[0] + up[0];
2162 v3f[ 7] = org[1] + right[1] + up[1];
2163 v3f[ 8] = org[2] + right[2] + up[2];
2164 v3f[ 9] = org[0] + right[0] - up[0];
2165 v3f[10] = org[1] + right[1] - up[1];
2166 v3f[11] = org[2] + right[2] - up[2];
2167 t2f[0] = tex->s1;t2f[1] = tex->t2;
2168 t2f[2] = tex->s1;t2f[3] = tex->t1;
2169 t2f[4] = tex->s2;t2f[5] = tex->t1;
2170 t2f[6] = tex->s2;t2f[7] = tex->t2;
2172 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2175 if (DotProduct(p->vel, r_view.origin) > DotProduct(p->vel, org))
2177 VectorNegate(p->vel, v);
2178 VectorVectors(v, right, up);
2181 VectorVectors(p->vel, right, up);
2182 VectorScale(right, size, right);
2183 VectorScale(up, size, up);
2184 v3f[ 0] = org[0] - right[0] - up[0];
2185 v3f[ 1] = org[1] - right[1] - up[1];
2186 v3f[ 2] = org[2] - right[2] - up[2];
2187 v3f[ 3] = org[0] - right[0] + up[0];
2188 v3f[ 4] = org[1] - right[1] + up[1];
2189 v3f[ 5] = org[2] - right[2] + up[2];
2190 v3f[ 6] = org[0] + right[0] + up[0];
2191 v3f[ 7] = org[1] + right[1] + up[1];
2192 v3f[ 8] = org[2] + right[2] + up[2];
2193 v3f[ 9] = org[0] + right[0] - up[0];
2194 v3f[10] = org[1] + right[1] - up[1];
2195 v3f[11] = org[2] + right[2] - up[2];
2196 t2f[0] = tex->s1;t2f[1] = tex->t2;
2197 t2f[2] = tex->s1;t2f[3] = tex->t1;
2198 t2f[4] = tex->s2;t2f[5] = tex->t1;
2199 t2f[6] = tex->s2;t2f[7] = tex->t2;
2201 else if (p->type->orientation == PARTICLE_SPARK)
2203 VectorMA(org, -0.02, p->vel, v);
2204 VectorMA(org, 0.02, p->vel, up2);
2205 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2206 t2f[0] = tex->s1;t2f[1] = tex->t2;
2207 t2f[2] = tex->s1;t2f[3] = tex->t1;
2208 t2f[4] = tex->s2;t2f[5] = tex->t1;
2209 t2f[6] = tex->s2;t2f[7] = tex->t2;
2211 else if (p->type->orientation == PARTICLE_BEAM)
2213 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2214 VectorSubtract(p->vel, org, up);
2215 VectorNormalize(up);
2216 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2217 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2218 t2f[0] = 1;t2f[1] = v[0];
2219 t2f[2] = 0;t2f[3] = v[0];
2220 t2f[4] = 0;t2f[5] = v[1];
2221 t2f[6] = 1;t2f[7] = v[1];
2225 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2230 // now render batches of particles based on blendmode and texture
2231 blendmode = PBLEND_ADD;
2232 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2233 texture = particletexture[63].texture;
2234 R_Mesh_TexBind(0, R_GetTexture(texture));
2235 GL_LockArrays(0, numsurfaces*4);
2238 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2240 p = cl.particles + surfacelist[surfacelistindex];
2242 if (blendmode != p->type->blendmode)
2245 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2247 batchstart = surfacelistindex;
2248 blendmode = p->type->blendmode;
2249 if (blendmode == PBLEND_ALPHA)
2250 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2251 else if (blendmode == PBLEND_ADD)
2252 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2253 else //if (blendmode == PBLEND_MOD)
2254 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2256 if (texture != particletexture[p->texnum].texture)
2259 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2261 batchstart = surfacelistindex;
2262 texture = particletexture[p->texnum].texture;
2263 R_Mesh_TexBind(0, R_GetTexture(texture));
2269 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2270 GL_LockArrays(0, 0);
2273 void R_DrawParticles (void)
2276 float minparticledist;
2279 // LordHavoc: early out conditions
2280 if ((!cl.num_particles) || (!r_drawparticles.integer))
2283 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2285 // LordHavoc: only render if not too close
2286 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2290 r_refdef.stats.particles++;
2291 if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2292 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);