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_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
183 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
184 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
185 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
186 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
187 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "0", "enables decals (bullet holes, blood, etc)"};
188 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "0", "how long before decals start to fade away"};
189 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "20", "how long decals take to fade away"};
192 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
198 particleeffectinfo_t *info = NULL;
199 const char *text = textstart;
201 effectinfoindex = -1;
202 for (linenumber = 1;;linenumber++)
205 for (arrayindex = 0;arrayindex < 16;arrayindex++)
206 argv[arrayindex][0] = 0;
209 if (!COM_ParseToken(&text, true))
211 if (!strcmp(com_token, "\n"))
215 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
221 #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;}
222 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
223 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
224 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
225 #define readfloat(var) checkparms(2);var = atof(argv[1])
226 if (!strcmp(argv[0], "effect"))
231 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
233 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
236 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
238 if (particleeffectname[effectnameindex][0])
240 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
245 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
249 // if we run out of names, abort
250 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
252 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
255 info = particleeffectinfo + effectinfoindex;
256 info->effectnameindex = effectnameindex;
257 info->particletype = pt_alphastatic;
258 info->tex[0] = tex_particle;
259 info->tex[1] = tex_particle;
260 info->color[0] = 0xFFFFFF;
261 info->color[1] = 0xFFFFFF;
265 info->alpha[1] = 256;
266 info->alpha[2] = 256;
267 info->time[0] = 9999;
268 info->time[1] = 9999;
269 VectorSet(info->lightcolor, 1, 1, 1);
270 info->lightshadow = true;
271 info->lighttime = 9999;
273 else if (info == NULL)
275 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
278 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
279 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
280 else if (!strcmp(argv[0], "type"))
283 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
284 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
285 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
286 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
287 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
288 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
289 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
290 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
291 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
292 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
293 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
294 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
295 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
297 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
298 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
299 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
300 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
301 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
302 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
303 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
304 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
305 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
306 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
307 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
308 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
309 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
310 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
311 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
312 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
313 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
314 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
315 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
316 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
317 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
318 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
319 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
320 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
322 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
331 int CL_ParticleEffectIndexForName(const char *name)
334 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
335 if (!strcmp(particleeffectname[i], name))
340 const char *CL_ParticleEffectNameForIndex(int i)
342 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
344 return particleeffectname[i];
347 // MUST match effectnameindex_t in client.h
348 static const char *standardeffectnames[EFFECT_TOTAL] =
372 "TE_TEI_BIGEXPLOSION",
388 void CL_Particles_LoadEffectInfo(void)
391 unsigned char *filedata;
392 fs_offset_t filesize;
393 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
394 memset(particleeffectname, 0, sizeof(particleeffectname));
395 for (i = 0;i < EFFECT_TOTAL;i++)
396 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
397 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
400 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
410 void CL_ReadPointFile_f (void);
411 void CL_Particles_Init (void)
413 Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
414 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
416 Cvar_RegisterVariable (&cl_particles);
417 Cvar_RegisterVariable (&cl_particles_quality);
418 Cvar_RegisterVariable (&cl_particles_size);
419 Cvar_RegisterVariable (&cl_particles_quake);
420 Cvar_RegisterVariable (&cl_particles_blood);
421 Cvar_RegisterVariable (&cl_particles_blood_alpha);
422 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
423 Cvar_RegisterVariable (&cl_particles_explosions_smoke);
424 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
425 Cvar_RegisterVariable (&cl_particles_explosions_shell);
426 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
427 Cvar_RegisterVariable (&cl_particles_smoke);
428 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
429 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
430 Cvar_RegisterVariable (&cl_particles_sparks);
431 Cvar_RegisterVariable (&cl_particles_bubbles);
432 Cvar_RegisterVariable (&cl_decals);
433 Cvar_RegisterVariable (&cl_decals_time);
434 Cvar_RegisterVariable (&cl_decals_fadetime);
437 void CL_Particles_Shutdown (void)
441 // list of all 26 parameters:
442 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
443 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
444 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
445 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
446 // palpha - opacity of particle as 0-255 (can be more than 255)
447 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
448 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
449 // pgravity - how much effect gravity has on the particle (0-1)
450 // pbounce - how much bounce the particle has when it hits a surface (0-1), -1 makes a blood splat when it hits a surface, 0 does not even check for collisions
451 // px,py,pz - starting origin of particle
452 // pvx,pvy,pvz - starting velocity of particle
453 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
454 static particle_t *particle(particletype_t *ptype, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter)
459 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].type;cl.free_particle++);
460 if (cl.free_particle >= cl.max_particles)
462 part = &cl.particles[cl.free_particle++];
463 if (cl.num_particles < cl.free_particle)
464 cl.num_particles = cl.free_particle;
465 memset(part, 0, sizeof(*part));
467 l2 = (int)lhrandom(0.5, 256.5);
469 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
470 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
471 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
472 part->color[3] = 0xFF;
475 part->sizeincrease = psizeincrease;
476 part->alpha = palpha;
477 part->alphafade = palphafade;
478 part->gravity = pgravity;
479 part->bounce = pbounce;
481 part->org[0] = px + originjitter * v[0];
482 part->org[1] = py + originjitter * v[1];
483 part->org[2] = pz + originjitter * v[2];
484 part->vel[0] = pvx + velocityjitter * v[0];
485 part->vel[1] = pvy + velocityjitter * v[1];
486 part->vel[2] = pvz + velocityjitter * v[2];
488 part->airfriction = pairfriction;
489 part->liquidfriction = pliquidfriction;
493 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
496 if (!cl_decals.integer)
498 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);
503 p->ownermodel = cl.entities[p->owner].render.model;
504 VectorAdd(org, normal, p->org);
505 VectorCopy(normal, p->vel);
506 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
507 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
508 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
512 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
515 float bestfrac, bestorg[3], bestnormal[3];
517 int besthitent = 0, hitent;
520 for (i = 0;i < 32;i++)
523 VectorMA(org, maxdist, org2, org2);
524 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
525 // take the closest trace result that doesn't end up hitting a NOMARKS
526 // surface (sky for example)
527 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
529 bestfrac = trace.fraction;
531 VectorCopy(trace.endpos, bestorg);
532 VectorCopy(trace.plane.normal, bestnormal);
536 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
539 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount);
540 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)
543 matrix4x4_t tempmatrix;
544 VectorLerp(originmins, 0.5, originmaxs, center);
545 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
546 if (effectnameindex == EFFECT_SVC_PARTICLE)
548 if (cl_particles.integer)
550 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
552 CL_ParticleExplosion(center);
553 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
554 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
557 count *= cl_particles_quality.value;
558 for (;count > 0;count--)
560 int k = particlepalette[palettecolor + (rand()&7)];
561 if (cl_particles_quake.integer)
562 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);
563 else if (gamemode == GAME_GOODVSBAD2)
564 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);
566 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);
571 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
572 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
573 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
574 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
575 else if (effectnameindex == EFFECT_TE_SPIKE)
577 if (cl_particles_bulletimpacts.integer)
579 if (cl_particles_quake.integer)
581 if (cl_particles_smoke.integer)
582 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
585 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
588 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
589 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
591 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
593 if (cl_particles_bulletimpacts.integer)
595 if (cl_particles_quake.integer)
597 if (cl_particles_smoke.integer)
598 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
601 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
604 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
605 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
606 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);
608 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
610 if (cl_particles_bulletimpacts.integer)
612 if (cl_particles_quake.integer)
614 if (cl_particles_smoke.integer)
615 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
618 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
621 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
622 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
624 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
626 if (cl_particles_bulletimpacts.integer)
628 if (cl_particles_quake.integer)
630 if (cl_particles_smoke.integer)
631 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
634 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
637 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
638 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
639 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);
641 else if (effectnameindex == EFFECT_TE_BLOOD)
643 if (!cl_particles_blood.integer)
645 if (cl_particles_quake.integer)
646 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
649 static double bloodaccumulator = 0;
650 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
651 for (;bloodaccumulator > 0;bloodaccumulator--)
652 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);
655 else if (effectnameindex == EFFECT_TE_SPARK)
656 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count, 0);
657 else if (effectnameindex == EFFECT_TE_PLASMABURN)
659 // plasma scorch mark
660 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
661 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
662 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
664 else if (effectnameindex == EFFECT_TE_GUNSHOT)
666 if (cl_particles_bulletimpacts.integer)
668 if (cl_particles_quake.integer)
669 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
671 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
674 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
675 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
677 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
679 if (cl_particles_bulletimpacts.integer)
681 if (cl_particles_quake.integer)
682 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
684 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
687 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
688 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
689 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);
691 else if (effectnameindex == EFFECT_TE_EXPLOSION)
693 CL_ParticleExplosion(center);
694 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);
696 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
698 CL_ParticleExplosion(center);
699 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);
701 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
703 if (cl_particles_quake.integer)
706 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
709 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);
711 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);
715 CL_ParticleExplosion(center);
716 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);
718 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
719 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);
720 else if (effectnameindex == EFFECT_TE_FLAMEJET)
722 count *= cl_particles_quality.value;
724 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);
726 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
728 float i, j, inc, vel;
731 inc = 8 / cl_particles_quality.value;
732 for (i = -128;i < 128;i += inc)
734 for (j = -128;j < 128;j += inc)
736 dir[0] = j + lhrandom(0, inc);
737 dir[1] = i + lhrandom(0, inc);
739 org[0] = center[0] + dir[0];
740 org[1] = center[1] + dir[1];
741 org[2] = center[2] + lhrandom(0, 64);
742 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
743 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);
747 else if (effectnameindex == EFFECT_TE_TELEPORT)
749 float i, j, k, inc, vel;
752 inc = 8 / cl_particles_quality.value;
753 for (i = -16;i < 16;i += inc)
755 for (j = -16;j < 16;j += inc)
757 for (k = -24;k < 32;k += inc)
759 VectorSet(dir, i*8, j*8, k*8);
760 VectorNormalize(dir);
761 vel = lhrandom(50, 113);
762 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);
766 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);
767 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);
769 else if (effectnameindex == EFFECT_TE_TEI_G3)
770 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);
771 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
773 if (cl_particles_smoke.integer)
775 count *= 0.25f * cl_particles_quality.value;
777 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);
780 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
782 CL_ParticleExplosion(center);
783 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);
785 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
788 if (cl_stainmaps.integer)
789 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
790 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
791 if (cl_particles_smoke.integer)
792 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
793 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);
794 if (cl_particles_sparks.integer)
795 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
796 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);
797 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);
799 else if (effectnameindex == EFFECT_EF_FLAME)
801 count *= 300 * cl_particles_quality.value;
803 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);
804 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);
806 else if (effectnameindex == EFFECT_EF_STARDUST)
808 count *= 200 * cl_particles_quality.value;
810 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);
811 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);
813 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
817 int smoke, blood, bubbles, r, color;
819 if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
822 Vector4Set(light, 0, 0, 0, 0);
824 if (effectnameindex == EFFECT_TR_ROCKET)
825 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
826 else if (effectnameindex == EFFECT_TR_VORESPIKE)
828 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
829 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
831 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
833 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
834 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
838 matrix4x4_t tempmatrix;
839 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
840 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);
847 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
850 VectorSubtract(originmaxs, originmins, dir);
851 len = VectorNormalizeLength(dir);
854 dec = -ent->persistent.trail_time;
855 ent->persistent.trail_time += len;
856 if (ent->persistent.trail_time < 0.01f)
859 // if we skip out, leave it reset
860 ent->persistent.trail_time = 0.0f;
865 // advance into this frame to reach the first puff location
866 VectorMA(originmins, dec, dir, pos);
869 smoke = cl_particles.integer && cl_particles_smoke.integer;
870 blood = cl_particles.integer && cl_particles_blood.integer;
871 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
872 qd = 1.0f / cl_particles_quality.value;
879 if (effectnameindex == EFFECT_TR_BLOOD)
881 if (cl_particles_quake.integer)
883 color = particlepalette[67 + (rand()&3)];
884 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);
889 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);
892 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
894 if (cl_particles_quake.integer)
897 color = particlepalette[67 + (rand()&3)];
898 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);
903 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);
909 if (effectnameindex == EFFECT_TR_ROCKET)
911 if (cl_particles_quake.integer)
914 color = particlepalette[ramp3[r]];
915 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);
919 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);
920 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);
923 else if (effectnameindex == EFFECT_TR_GRENADE)
925 if (cl_particles_quake.integer)
928 color = particlepalette[ramp3[r]];
929 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);
933 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);
936 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
938 if (cl_particles_quake.integer)
941 color = particlepalette[52 + (rand()&7)];
942 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
943 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
945 else if (gamemode == GAME_GOODVSBAD2)
948 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);
952 color = particlepalette[20 + (rand()&7)];
953 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);
956 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
958 if (cl_particles_quake.integer)
961 color = particlepalette[230 + (rand()&7)];
962 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);
963 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 color = particlepalette[226 + (rand()&7)];
968 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);
971 else if (effectnameindex == EFFECT_TR_VORESPIKE)
973 if (cl_particles_quake.integer)
975 color = particlepalette[152 + (rand()&3)];
976 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);
978 else if (gamemode == GAME_GOODVSBAD2)
981 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);
983 else if (gamemode == GAME_PRYDON)
986 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);
989 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);
991 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
994 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);
996 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
999 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);
1001 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1002 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);
1006 if (effectnameindex == EFFECT_TR_ROCKET)
1007 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);
1008 else if (effectnameindex == EFFECT_TR_GRENADE)
1009 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);
1011 // advance to next time and position
1014 VectorMA (pos, dec, dir, pos);
1017 ent->persistent.trail_time = len;
1019 else if (developer.integer >= 1)
1020 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1023 // this is also called on point effects with spawndlight = true and
1024 // spawnparticles = true
1025 // it is called CL_ParticleTrail because most code does not want to supply
1026 // these parameters, only trail handling does
1027 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)
1030 qboolean found = false;
1031 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1032 return; // invalid effect index
1033 if (!particleeffectname[effectnameindex][0])
1034 return; // no such effect
1035 VectorLerp(originmins, 0.5, originmaxs, center);
1036 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1038 int effectinfoindex;
1041 particleeffectinfo_t *info;
1043 vec3_t centervelocity;
1049 qboolean underwater;
1050 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1051 VectorLerp(originmins, 0.5, originmaxs, center);
1052 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1053 supercontents = CL_PointSuperContents(center);
1054 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1055 VectorSubtract(originmaxs, originmins, traildir);
1056 traillen = VectorLength(traildir);
1057 VectorNormalize(traildir);
1058 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1060 if (info->effectnameindex == effectnameindex)
1063 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1065 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1068 // spawn a dlight if requested
1069 if (info->lightradiusstart > 0 && spawndlight)
1071 matrix4x4_t tempmatrix;
1072 if (info->trailspacing > 0)
1073 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1075 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1076 if (info->lighttime > 0 && info->lightradiusfade > 0)
1078 // light flash (explosion, etc)
1079 // called when effect starts
1080 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);
1085 // called by CL_LinkNetworkEntity
1086 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1087 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);
1091 if (!spawnparticles)
1096 if (info->tex[1] > info->tex[0])
1098 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1099 tex = min(tex, info->tex[1] - 1);
1101 if (info->particletype == pt_decal)
1102 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]);
1103 else if (info->particletype == pt_beam)
1104 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);
1107 if (!cl_particles.integer)
1109 switch (info->particletype)
1111 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1112 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1113 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1114 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1117 VectorCopy(originmins, trailpos);
1118 if (info->trailspacing > 0)
1120 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1121 trailstep = info->trailspacing / cl_particles_quality.value;
1125 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1128 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1129 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1131 if (info->tex[1] > info->tex[0])
1133 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1134 tex = min(tex, info->tex[1] - 1);
1138 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1139 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1140 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1143 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);
1145 VectorMA(trailpos, trailstep, traildir, trailpos);
1152 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1155 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)
1157 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1165 void CL_EntityParticles (const entity_t *ent)
1168 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1169 static vec3_t avelocities[NUMVERTEXNORMALS];
1170 if (!cl_particles.integer) return;
1172 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1174 if (!avelocities[0][0])
1175 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1176 avelocities[0][i] = lhrandom(0, 2.55);
1178 for (i = 0;i < NUMVERTEXNORMALS;i++)
1180 yaw = cl.time * avelocities[i][0];
1181 pitch = cl.time * avelocities[i][1];
1182 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1183 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1184 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1185 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);
1190 void CL_ReadPointFile_f (void)
1192 vec3_t org, leakorg;
1194 char *pointfile = NULL, *pointfilepos, *t, tchar;
1195 char name[MAX_OSPATH];
1200 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1201 strlcat (name, ".pts", sizeof (name));
1202 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1205 Con_Printf("Could not open %s\n", name);
1209 Con_Printf("Reading %s...\n", name);
1210 VectorClear(leakorg);
1213 pointfilepos = pointfile;
1214 while (*pointfilepos)
1216 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1221 while (*t && *t != '\n' && *t != '\r')
1225 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1231 VectorCopy(org, leakorg);
1234 if (cl.num_particles < cl.max_particles - 3)
1237 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);
1240 Mem_Free(pointfile);
1241 VectorCopy(leakorg, org);
1242 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1244 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);
1245 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);
1246 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);
1251 CL_ParseParticleEffect
1253 Parse an effect out of the server message
1256 void CL_ParseParticleEffect (void)
1259 int i, count, msgcount, color;
1261 MSG_ReadVector(org, cls.protocol);
1262 for (i=0 ; i<3 ; i++)
1263 dir[i] = MSG_ReadChar ();
1264 msgcount = MSG_ReadByte ();
1265 color = MSG_ReadByte ();
1267 if (msgcount == 255)
1272 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1277 CL_ParticleExplosion
1281 void CL_ParticleExplosion (const vec3_t org)
1287 if (cl_stainmaps.integer)
1288 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1289 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1291 if (cl_particles_quake.integer)
1293 for (i = 0;i < 1024;i++)
1299 color = particlepalette[ramp1[r]];
1300 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);
1304 color = particlepalette[ramp2[r]];
1305 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);
1311 i = CL_PointSuperContents(org);
1312 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1314 if (cl_particles.integer && cl_particles_bubbles.integer)
1315 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1316 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);
1320 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1322 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1324 for (i = 0;i < 32;i++)
1328 for (k = 0;k < 16;k++)
1330 v[0] = org[0] + lhrandom(-48, 48);
1331 v[1] = org[1] + lhrandom(-48, 48);
1332 v[2] = org[2] + lhrandom(-48, 48);
1333 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1334 if (trace.fraction >= 0.1)
1337 VectorSubtract(trace.endpos, org, v2);
1338 VectorScale(v2, 2.0f, v2);
1339 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);
1343 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1344 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1345 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);
1349 if (cl_particles_explosions_shell.integer)
1350 R_NewExplosion(org);
1355 CL_ParticleExplosion2
1359 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1362 if (!cl_particles.integer) return;
1364 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1366 k = particlepalette[colorStart + (i % colorLength)];
1367 if (cl_particles_quake.integer)
1368 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);
1370 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);
1374 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1376 if (cl_particles_sparks.integer)
1378 sparkcount *= cl_particles_quality.value;
1379 while(sparkcount-- > 0)
1380 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);
1382 if (cl_particles_smoke.integer)
1384 smokecount *= cl_particles_quality.value;
1385 while(smokecount-- > 0)
1386 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);
1390 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)
1393 if (!cl_particles.integer) return;
1395 count = (int)(count * cl_particles_quality.value);
1398 k = particlepalette[colorbase + (rand()&3)];
1399 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);
1403 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1406 float z, minz, maxz;
1408 if (!cl_particles.integer) return;
1409 if (dir[2] < 0) // falling
1414 minz = z - fabs(dir[2]) * 0.1;
1415 maxz = z + fabs(dir[2]) * 0.1;
1416 minz = bound(mins[2], minz, maxs[2]);
1417 maxz = bound(mins[2], maxz, maxs[2]);
1419 count = (int)(count * cl_particles_quality.value);
1424 count *= 4; // ick, this should be in the mod or maps?
1428 k = particlepalette[colorbase + (rand()&3)];
1429 if (gamemode == GAME_GOODVSBAD2)
1430 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);
1432 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);
1438 k = particlepalette[colorbase + (rand()&3)];
1439 if (gamemode == GAME_GOODVSBAD2)
1440 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);
1442 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);
1444 VectorCopy(p->vel, p->relativedirection);
1448 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1457 void CL_MoveParticles (void)
1460 int i, maxparticle, j, a, content;
1461 float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
1462 particletype_t *decaltype, *bloodtype;
1466 // LordHavoc: early out condition
1467 if (!cl.num_particles)
1469 cl.free_particle = 0;
1473 frametime = bound(0, cl.time - cl.oldtime, 0.1);
1474 gravity = frametime * sv_gravity.value;
1475 dvel = 1+4*frametime;
1476 decalfade = frametime * 255 / cl_decals_fadetime.value;
1477 decaltype = particletype + pt_decal;
1478 bloodtype = particletype + pt_blood;
1482 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1486 if (cl.free_particle > i)
1487 cl.free_particle = i;
1492 // heavily optimized decal case
1493 if (p->type == decaltype)
1495 // FIXME: this has fairly wacky handling of alpha
1496 if (cl.time > p->time2 + cl_decals_time.value)
1498 p->alpha -= decalfade;
1502 if (cl.free_particle > i)
1503 cl.free_particle = i;
1509 if (cl.entities[p->owner].render.model == p->ownermodel)
1511 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1512 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1517 if (cl.free_particle > i)
1518 cl.free_particle = i;
1526 p->alpha -= p->alphafade * frametime;
1531 if (cl.free_particle > i)
1532 cl.free_particle = i;
1536 if (p->type->orientation != PARTICLE_BEAM)
1538 VectorCopy(p->org, oldorg);
1539 VectorMA(p->org, frametime, p->vel, p->org);
1540 VectorCopy(p->org, org);
1543 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);
1544 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1545 // or if the trace hit something flagged as NOIMPACT
1546 // then remove the particle
1547 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1552 // react if the particle hit something
1553 if (trace.fraction < 1)
1555 VectorCopy(trace.endpos, p->org);
1556 if (p->type == particletype + pt_rain)
1558 // raindrop - splash on solid/water/slime/lava
1560 // convert from a raindrop particle to a rainsplash decal
1561 VectorCopy(trace.plane.normal, p->vel);
1562 VectorAdd(p->org, p->vel, p->org);
1563 p->type = particletype + pt_raindecal;
1564 p->texnum = tex_rainsplash;
1566 p->alphafade = p->alpha / 0.4;
1569 p->liquidfriction = 0;
1572 p->sizeincrease = p->size * 16;
1575 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);
1577 else if (p->type == bloodtype)
1579 // blood - splash on solid
1580 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1585 if (!cl_decals.integer)
1590 // convert from a blood particle to a blood decal
1591 VectorCopy(trace.plane.normal, p->vel);
1592 VectorAdd(p->org, p->vel, p->org);
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)));
1596 p->type = particletype + pt_decal;
1597 p->texnum = tex_blooddecal[rand()&7];
1599 p->ownermodel = cl.entities[hitent].render.model;
1600 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1601 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1602 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1607 p->liquidfriction = 0;
1611 else if (p->bounce < 0)
1613 // bounce -1 means remove on impact
1619 // anything else - bounce off solid
1620 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1621 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1622 if (DotProduct(p->vel, p->vel) < 0.03)
1623 VectorClear(p->vel);
1627 p->vel[2] -= p->gravity * gravity;
1629 if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1631 f = 1.0f - min(p->liquidfriction * frametime, 1);
1632 VectorScale(p->vel, f, p->vel);
1634 else if (p->airfriction)
1636 f = 1.0f - min(p->airfriction * frametime, 1);
1637 VectorScale(p->vel, f, p->vel);
1641 if (p->type != particletype + pt_static)
1643 switch (p->type - particletype)
1645 case pt_entityparticle:
1646 // particle that removes itself after one rendered frame
1653 a = CL_PointSuperContents(p->org);
1654 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1656 p->size += frametime * 8;
1657 //p->alpha -= bloodwaterfade;
1660 p->vel[2] -= gravity;
1661 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1665 a = CL_PointSuperContents(p->org);
1666 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1673 a = CL_PointSuperContents(p->org);
1674 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1678 if (cl.time > p->time2)
1681 p->time2 = cl.time + (rand() & 3) * 0.1;
1682 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1683 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1684 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1686 a = CL_PointSuperContents(p->org);
1687 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1695 cl.num_particles = maxparticle + 1;
1698 #define MAX_PARTICLETEXTURES 64
1699 // particletexture_t is a rectangle in the particlefonttexture
1700 typedef struct particletexture_s
1702 rtexture_t *texture;
1703 float s1, t1, s2, t2;
1707 static rtexturepool_t *particletexturepool;
1708 static rtexture_t *particlefonttexture;
1709 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1711 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1713 #define PARTICLETEXTURESIZE 64
1714 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1716 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1720 dz = 1 - (dx*dx+dy*dy);
1721 if (dz > 0) // it does hit the sphere
1725 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1726 VectorNormalize(normal);
1727 dot = DotProduct(normal, light);
1728 if (dot > 0.5) // interior reflection
1729 f += ((dot * 2) - 1);
1730 else if (dot < -0.5) // exterior reflection
1731 f += ((dot * -2) - 1);
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 f += 16; // just to give it a haze so you can see the outline
1742 f = bound(0, f, 255);
1743 return (unsigned char) f;
1749 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1751 int basex, basey, y;
1752 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1753 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1754 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1755 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1758 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1761 float cx, cy, dx, dy, f, iradius;
1763 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1764 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1765 iradius = 1.0f / radius;
1766 alpha *= (1.0f / 255.0f);
1767 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1769 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1773 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1776 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1777 d[0] += (int)(f * (red - d[0]));
1778 d[1] += (int)(f * (green - d[1]));
1779 d[2] += (int)(f * (blue - d[2]));
1785 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1788 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1790 data[0] = bound(minr, data[0], maxr);
1791 data[1] = bound(ming, data[1], maxg);
1792 data[2] = bound(minb, data[2], maxb);
1796 void particletextureinvert(unsigned char *data)
1799 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1801 data[0] = 255 - data[0];
1802 data[1] = 255 - data[1];
1803 data[2] = 255 - data[2];
1807 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1808 static void R_InitBloodTextures (unsigned char *particletexturedata)
1811 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1814 for (i = 0;i < 8;i++)
1816 memset(&data[0][0][0], 255, sizeof(data));
1817 for (k = 0;k < 24;k++)
1818 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1819 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1820 particletextureinvert(&data[0][0][0]);
1821 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1825 for (i = 0;i < 8;i++)
1827 memset(&data[0][0][0], 255, sizeof(data));
1829 for (j = 1;j < 10;j++)
1830 for (k = min(j, m - 1);k < m;k++)
1831 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1832 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1833 particletextureinvert(&data[0][0][0]);
1834 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1839 //uncomment this to make engine save out particle font to a tga file when run
1840 //#define DUMPPARTICLEFONT
1842 static void R_InitParticleTexture (void)
1844 int x, y, d, i, k, m;
1848 // a note: decals need to modulate (multiply) the background color to
1849 // properly darken it (stain), and they need to be able to alpha fade,
1850 // this is a very difficult challenge because it means fading to white
1851 // (no change to background) rather than black (darkening everything
1852 // behind the whole decal polygon), and to accomplish this the texture is
1853 // inverted (dark red blood on white background becomes brilliant cyan
1854 // and white on black background) so we can alpha fade it to black, then
1855 // we invert it again during the blendfunc to make it work...
1857 #ifndef DUMPPARTICLEFONT
1858 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1859 if (!particlefonttexture)
1862 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1863 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1864 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1867 for (i = 0;i < 8;i++)
1869 memset(&data[0][0][0], 255, sizeof(data));
1872 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1874 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1875 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1877 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1879 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1880 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1882 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1883 d = (noise2[y][x] - 128) * 3 + 192;
1885 d = (int)(d * (1-(dx*dx+dy*dy)));
1886 d = (d * noise1[y][x]) >> 7;
1887 d = bound(0, d, 255);
1888 data[y][x][3] = (unsigned char) d;
1895 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1899 memset(&data[0][0][0], 255, sizeof(data));
1900 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1902 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1903 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1905 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1906 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1907 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1910 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1913 memset(&data[0][0][0], 255, sizeof(data));
1914 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1916 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1917 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1919 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1920 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1921 d = bound(0, d, 255);
1922 data[y][x][3] = (unsigned char) d;
1925 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1928 memset(&data[0][0][0], 255, sizeof(data));
1929 light[0] = 1;light[1] = 1;light[2] = 1;
1930 VectorNormalize(light);
1931 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1933 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1934 // stretch upper half of bubble by +50% and shrink lower half by -50%
1935 // (this gives an elongated teardrop shape)
1937 dy = (dy - 0.5f) * 2.0f;
1939 dy = (dy - 0.5f) / 1.5f;
1940 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1942 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1943 // shrink bubble width to half
1945 data[y][x][3] = shadebubble(dx, dy, light);
1948 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1951 memset(&data[0][0][0], 255, sizeof(data));
1952 light[0] = 1;light[1] = 1;light[2] = 1;
1953 VectorNormalize(light);
1954 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1956 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1957 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1959 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1960 data[y][x][3] = shadebubble(dx, dy, light);
1963 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1965 // Blood particles and blood decals
1966 R_InitBloodTextures (particletexturedata);
1969 for (i = 0;i < 8;i++)
1971 memset(&data[0][0][0], 255, sizeof(data));
1972 for (k = 0;k < 12;k++)
1973 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1974 for (k = 0;k < 3;k++)
1975 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1976 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1977 particletextureinvert(&data[0][0][0]);
1978 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1981 #ifdef DUMPPARTICLEFONT
1982 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1985 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1987 Mem_Free(particletexturedata);
1989 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1991 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1992 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1993 particletexture[i].texture = particlefonttexture;
1994 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1995 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1996 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1997 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2000 #ifndef DUMPPARTICLEFONT
2001 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
2002 if (!particletexture[tex_beam].texture)
2005 unsigned char noise3[64][64], data2[64][16][4];
2007 fractalnoise(&noise3[0][0], 64, 4);
2009 for (y = 0;y < 64;y++)
2011 dy = (y - 0.5f*64) / (64*0.5f-1);
2012 for (x = 0;x < 16;x++)
2014 dx = (x - 0.5f*16) / (16*0.5f-2);
2015 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2016 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2017 data2[y][x][3] = 255;
2021 #ifdef DUMPPARTICLEFONT
2022 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2024 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2026 particletexture[tex_beam].s1 = 0;
2027 particletexture[tex_beam].t1 = 0;
2028 particletexture[tex_beam].s2 = 1;
2029 particletexture[tex_beam].t2 = 1;
2032 static void r_part_start(void)
2034 particletexturepool = R_AllocTexturePool();
2035 R_InitParticleTexture ();
2036 CL_Particles_LoadEffectInfo();
2039 static void r_part_shutdown(void)
2041 R_FreeTexturePool(&particletexturepool);
2044 static void r_part_newmap(void)
2048 #define BATCHSIZE 256
2049 int particle_element3i[BATCHSIZE*6];
2050 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2052 void R_Particles_Init (void)
2055 for (i = 0;i < BATCHSIZE;i++)
2057 particle_element3i[i*6+0] = i*4+0;
2058 particle_element3i[i*6+1] = i*4+1;
2059 particle_element3i[i*6+2] = i*4+2;
2060 particle_element3i[i*6+3] = i*4+0;
2061 particle_element3i[i*6+4] = i*4+2;
2062 particle_element3i[i*6+5] = i*4+3;
2065 Cvar_RegisterVariable(&r_drawparticles);
2066 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2069 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2071 int surfacelistindex;
2072 int batchstart, batchcount;
2073 const particle_t *p;
2075 rtexture_t *texture;
2076 float *v3f, *t2f, *c4f;
2078 R_Mesh_Matrix(&identitymatrix);
2079 R_Mesh_ResetTextureState();
2080 R_Mesh_VertexPointer(particle_vertex3f);
2081 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f);
2082 R_Mesh_ColorPointer(particle_color4f);
2083 GL_DepthMask(false);
2085 GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
2087 // first generate all the vertices at once
2088 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2090 particletexture_t *tex;
2092 float up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
2094 p = cl.particles + surfacelist[surfacelistindex];
2096 blendmode = p->type->blendmode;
2098 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2099 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2100 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2101 ca = p->alpha * (1.0f / 255.0f);
2102 if (blendmode == PBLEND_MOD)
2112 ca /= cl_particles_quality.value;
2113 if (p->type->lighting)
2115 float ambient[3], diffuse[3], diffusenormal[3];
2116 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2117 cr *= (ambient[0] + 0.5 * diffuse[0]);
2118 cg *= (ambient[1] + 0.5 * diffuse[1]);
2119 cb *= (ambient[2] + 0.5 * diffuse[2]);
2121 if (r_refdef.fogenabled)
2123 fog = VERTEXFOGTABLE(VectorDistance(p->org, r_view.origin));
2128 if (blendmode == PBLEND_ALPHA)
2130 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2131 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2132 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2135 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2136 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2137 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2138 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2140 size = p->size * cl_particles_size.value;
2142 tex = &particletexture[p->texnum];
2143 if (p->type->orientation == PARTICLE_BILLBOARD)
2145 VectorScale(r_view.left, -size, right);
2146 VectorScale(r_view.up, size, up);
2147 v3f[ 0] = org[0] - right[0] - up[0];
2148 v3f[ 1] = org[1] - right[1] - up[1];
2149 v3f[ 2] = org[2] - right[2] - up[2];
2150 v3f[ 3] = org[0] - right[0] + up[0];
2151 v3f[ 4] = org[1] - right[1] + up[1];
2152 v3f[ 5] = org[2] - right[2] + up[2];
2153 v3f[ 6] = org[0] + right[0] + up[0];
2154 v3f[ 7] = org[1] + right[1] + up[1];
2155 v3f[ 8] = org[2] + right[2] + up[2];
2156 v3f[ 9] = org[0] + right[0] - up[0];
2157 v3f[10] = org[1] + right[1] - up[1];
2158 v3f[11] = org[2] + right[2] - up[2];
2159 t2f[0] = tex->s1;t2f[1] = tex->t2;
2160 t2f[2] = tex->s1;t2f[3] = tex->t1;
2161 t2f[4] = tex->s2;t2f[5] = tex->t1;
2162 t2f[6] = tex->s2;t2f[7] = tex->t2;
2164 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2167 if (DotProduct(p->vel, r_view.origin) > DotProduct(p->vel, org))
2169 VectorNegate(p->vel, v);
2170 VectorVectors(v, right, up);
2173 VectorVectors(p->vel, right, up);
2174 VectorScale(right, size, right);
2175 VectorScale(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_SPARK)
2195 VectorMA(org, -0.02, p->vel, v);
2196 VectorMA(org, 0.02, p->vel, up2);
2197 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2198 t2f[0] = tex->s1;t2f[1] = tex->t2;
2199 t2f[2] = tex->s1;t2f[3] = tex->t1;
2200 t2f[4] = tex->s2;t2f[5] = tex->t1;
2201 t2f[6] = tex->s2;t2f[7] = tex->t2;
2203 else if (p->type->orientation == PARTICLE_BEAM)
2205 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2206 VectorSubtract(p->vel, org, up);
2207 VectorNormalize(up);
2208 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2209 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2210 t2f[0] = 1;t2f[1] = v[0];
2211 t2f[2] = 0;t2f[3] = v[0];
2212 t2f[4] = 0;t2f[5] = v[1];
2213 t2f[6] = 1;t2f[7] = v[1];
2217 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2222 // now render batches of particles based on blendmode and texture
2223 blendmode = PBLEND_ADD;
2224 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2225 texture = particletexture[63].texture;
2226 R_Mesh_TexBind(0, R_GetTexture(texture));
2227 GL_LockArrays(0, numsurfaces*4);
2230 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2232 p = cl.particles + surfacelist[surfacelistindex];
2234 if (blendmode != p->type->blendmode)
2237 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2239 batchstart = surfacelistindex;
2240 blendmode = p->type->blendmode;
2241 if (blendmode == PBLEND_ALPHA)
2242 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2243 else if (blendmode == PBLEND_ADD)
2244 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2245 else //if (blendmode == PBLEND_MOD)
2246 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2248 if (texture != particletexture[p->texnum].texture)
2251 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2253 batchstart = surfacelistindex;
2254 texture = particletexture[p->texnum].texture;
2255 R_Mesh_TexBind(0, R_GetTexture(texture));
2261 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2262 GL_LockArrays(0, 0);
2265 void R_DrawParticles (void)
2268 float minparticledist;
2271 // LordHavoc: early out conditions
2272 if ((!cl.num_particles) || (!r_drawparticles.integer))
2275 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2277 // LordHavoc: only render if not too close
2278 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2282 r_refdef.stats.particles++;
2283 if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2284 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);