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_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
47 #define PARTICLEEFFECT_DEFINED 2147483648U
49 typedef struct particleeffectinfo_s
51 int effectnameindex; // which effect this belongs to
52 // PARTICLEEFFECT_* bits
54 // blood effects may spawn very few particles, so proper fraction-overflow
55 // handling is very important, this variable keeps track of the fraction
56 double particleaccumulator;
57 // the math is: countabsolute + requestedcount * countmultiplier * quality
58 // absolute number of particles to spawn, often used for decals
59 // (unaffected by quality and requestedcount)
61 // multiplier for the number of particles CL_ParticleEffect was told to
62 // spawn, most effects do not really have a count and hence use 1, so
63 // this is often the actual count to spawn, not merely a multiplier
64 float countmultiplier;
65 // if > 0 this causes the particle to spawn in an evenly spaced line from
66 // originmins to originmaxs (causing them to describe a trail, not a box)
68 // type of particle to spawn (defines some aspects of behavior)
70 // blending mode used on this particle type
72 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
73 porientation_t orientation;
74 // range of colors to choose from in hex RRGGBB (like HTML color tags),
75 // randomly interpolated at spawn
76 unsigned int color[2];
77 // a random texture is chosen in this range (note the second value is one
78 // past the last choosable, so for example 8,16 chooses any from 8 up and
80 // if start and end of the range are the same, no randomization is done
82 // range of size values randomly chosen when spawning, plus size increase over time
84 // range of alpha values randomly chosen when spawning, plus alpha fade
86 // how long the particle should live (note it is also removed if alpha drops to 0)
88 // how much gravity affects this particle (negative makes it fly up!)
90 // how much bounce the particle has when it hits a surface
91 // if negative the particle is removed on impact
93 // if in air this friction is applied
94 // if negative the particle accelerates
96 // if in liquid (water/slime/lava) this friction is applied
97 // if negative the particle accelerates
99 // these offsets are added to the values given to particleeffect(), and
100 // then an ellipsoid-shaped jitter is added as defined by these
101 // (they are the 3 radii)
103 // stretch velocity factor (used for sparks)
104 float originoffset[3];
105 float relativeoriginoffset[3];
106 float velocityoffset[3];
107 float relativevelocityoffset[3];
108 float originjitter[3];
109 float velocityjitter[3];
110 float velocitymultiplier;
111 // an effect can also spawn a dlight
112 float lightradiusstart;
113 float lightradiusfade;
116 qboolean lightshadow;
118 float lightcorona[2];
119 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
124 float rotate[4]; // min/max base angle, min/max rotation over time
126 particleeffectinfo_t;
128 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
130 int numparticleeffectinfo;
131 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
133 static int particlepalette[256];
135 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
136 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
137 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
138 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
139 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
140 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
141 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
142 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
143 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
144 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
145 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
146 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
147 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
148 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
149 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
150 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
151 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
152 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
153 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
154 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
155 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
156 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
157 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
158 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
159 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
160 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
161 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
162 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
163 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
164 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
165 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
166 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
169 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
170 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
171 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
173 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
175 // particletexture_t is a rectangle in the particlefonttexture
176 typedef struct particletexture_s
179 float s1, t1, s2, t2;
183 static rtexturepool_t *particletexturepool;
184 static rtexture_t *particlefonttexture;
185 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
186 skinframe_t *decalskinframe;
188 // texture numbers in particle font
189 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
190 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
191 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
192 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
193 static const int tex_rainsplash = 32;
194 static const int tex_particle = 63;
195 static const int tex_bubble = 62;
196 static const int tex_raindrop = 61;
197 static const int tex_beam = 60;
199 particleeffectinfo_t baselineparticleeffectinfo =
201 0, //int effectnameindex; // which effect this belongs to
202 // PARTICLEEFFECT_* bits
204 // blood effects may spawn very few particles, so proper fraction-overflow
205 // handling is very important, this variable keeps track of the fraction
206 0.0, //double particleaccumulator;
207 // the math is: countabsolute + requestedcount * countmultiplier * quality
208 // absolute number of particles to spawn, often used for decals
209 // (unaffected by quality and requestedcount)
210 0.0f, //float countabsolute;
211 // multiplier for the number of particles CL_ParticleEffect was told to
212 // spawn, most effects do not really have a count and hence use 1, so
213 // this is often the actual count to spawn, not merely a multiplier
214 0.0f, //float countmultiplier;
215 // if > 0 this causes the particle to spawn in an evenly spaced line from
216 // originmins to originmaxs (causing them to describe a trail, not a box)
217 0.0f, //float trailspacing;
218 // type of particle to spawn (defines some aspects of behavior)
219 pt_alphastatic, //ptype_t particletype;
220 // blending mode used on this particle type
221 PBLEND_ALPHA, //pblend_t blendmode;
222 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
223 PARTICLE_BILLBOARD, //porientation_t orientation;
224 // range of colors to choose from in hex RRGGBB (like HTML color tags),
225 // randomly interpolated at spawn
226 {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
227 // a random texture is chosen in this range (note the second value is one
228 // past the last choosable, so for example 8,16 chooses any from 8 up and
230 // if start and end of the range are the same, no randomization is done
231 {63, 63 /* tex_particle */}, //int tex[2];
232 // range of size values randomly chosen when spawning, plus size increase over time
233 {1, 1, 0.0f}, //float size[3];
234 // range of alpha values randomly chosen when spawning, plus alpha fade
235 {0.0f, 256.0f, 256.0f}, //float alpha[3];
236 // how long the particle should live (note it is also removed if alpha drops to 0)
237 {16777216.0f, 16777216.0f}, //float time[2];
238 // how much gravity affects this particle (negative makes it fly up!)
239 0.0f, //float gravity;
240 // how much bounce the particle has when it hits a surface
241 // if negative the particle is removed on impact
242 0.0f, //float bounce;
243 // if in air this friction is applied
244 // if negative the particle accelerates
245 0.0f, //float airfriction;
246 // if in liquid (water/slime/lava) this friction is applied
247 // if negative the particle accelerates
248 0.0f, //float liquidfriction;
249 // these offsets are added to the values given to particleeffect(), and
250 // then an ellipsoid-shaped jitter is added as defined by these
251 // (they are the 3 radii)
252 1.0f, //float stretchfactor;
253 // stretch velocity factor (used for sparks)
254 {0.0f, 0.0f, 0.0f}, //float originoffset[3];
255 {0.0f, 0.0f, 0.0f}, //float relativeoriginoffset[3];
256 {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
257 {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3];
258 {0.0f, 0.0f, 0.0f}, //float originjitter[3];
259 {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
260 0.0f, //float velocitymultiplier;
261 // an effect can also spawn a dlight
262 0.0f, //float lightradiusstart;
263 0.0f, //float lightradiusfade;
264 16777216.0f, //float lighttime;
265 {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
266 true, //qboolean lightshadow;
267 0, //int lightcubemapnum;
268 {1.0f, 0.25f}, //float lightcorona[2];
269 {(unsigned int)-1, (unsigned int)-1}, //unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
270 {-1, -1}, //int staintex[2];
271 {1.0f, 1.0f}, //float stainalpha[2];
272 {2.0f, 2.0f}, //float stainsize[2];
274 {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
277 cvar_t cl_particles = {CVAR_CLIENT | CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
278 cvar_t cl_particles_quality = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
279 cvar_t cl_particles_alpha = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
280 cvar_t cl_particles_size = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
281 cvar_t cl_particles_quake = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
282 cvar_t cl_particles_blood = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
283 cvar_t cl_particles_blood_alpha = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
284 cvar_t cl_particles_blood_decal_alpha = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
285 cvar_t cl_particles_blood_decal_scalemin = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
286 cvar_t cl_particles_blood_decal_scalemax = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
287 cvar_t cl_particles_blood_bloodhack = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
288 cvar_t cl_particles_bulletimpacts = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
289 cvar_t cl_particles_explosions_sparks = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
290 cvar_t cl_particles_explosions_shell = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
291 cvar_t cl_particles_rain = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
292 cvar_t cl_particles_snow = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
293 cvar_t cl_particles_smoke = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
294 cvar_t cl_particles_smoke_alpha = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
295 cvar_t cl_particles_smoke_alphafade = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
296 cvar_t cl_particles_sparks = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
297 cvar_t cl_particles_bubbles = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
298 cvar_t cl_particles_visculling = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
299 cvar_t cl_particles_collisions = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"};
300 cvar_t cl_particles_forcetraileffects = {CVAR_CLIENT, "cl_particles_forcetraileffects", "0", "force trails to be displayed even if a non-trail draw primitive was used (debug/compat feature)"};
301 cvar_t cl_decals = {CVAR_CLIENT | CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
302 cvar_t cl_decals_time = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
303 cvar_t cl_decals_fadetime = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
304 cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
305 cvar_t cl_decals_newsystem_immediatebloodstain = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"};
306 cvar_t cl_decals_newsystem_bloodsmears = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_newsystem_bloodsmears", "1", "enable use of particle velocity as decal projection direction rather than surface normal"};
307 cvar_t cl_decals_models = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models"};
308 cvar_t cl_decals_bias = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
309 cvar_t cl_decals_max = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
312 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
318 particleeffectinfo_t *info = NULL;
319 const char *text = textstart;
321 for (linenumber = 1;;linenumber++)
324 for (arrayindex = 0;arrayindex < 16;arrayindex++)
325 argv[arrayindex][0] = 0;
328 if (!COM_ParseToken_Simple(&text, true, false, true))
330 if (!strcmp(com_token, "\n"))
334 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
340 #define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;}
341 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
342 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
343 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
344 #define readfloat(var) checkparms(2);var = atof(argv[1])
345 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
346 if (!strcmp(argv[0], "effect"))
350 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
352 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
355 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
357 if (particleeffectname[effectnameindex][0])
359 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
364 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
368 // if we run out of names, abort
369 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
371 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
374 for(i = 0; i < numparticleeffectinfo; ++i)
376 info = particleeffectinfo + i;
377 if(!(info->flags & PARTICLEEFFECT_DEFINED))
378 if(info->effectnameindex == effectnameindex)
381 if(i < numparticleeffectinfo)
383 info = particleeffectinfo + numparticleeffectinfo++;
384 // copy entire info from baseline, then fix up the nameindex
385 *info = baselineparticleeffectinfo;
386 info->effectnameindex = effectnameindex;
389 else if (info == NULL)
391 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
395 info->flags |= PARTICLEEFFECT_DEFINED;
396 if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
397 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
398 else if (!strcmp(argv[0], "type"))
401 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
402 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
403 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
404 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
405 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
406 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
407 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
408 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
409 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
410 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
411 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
412 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
413 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
414 info->blendmode = particletype[info->particletype].blendmode;
415 info->orientation = particletype[info->particletype].orientation;
417 else if (!strcmp(argv[0], "blend"))
420 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
421 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
422 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
423 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
425 else if (!strcmp(argv[0], "orientation"))
428 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
429 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
430 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
431 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
432 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
434 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
435 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
436 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
437 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
438 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
439 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
440 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
441 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
442 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
443 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
444 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
445 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
446 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
447 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
448 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
449 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
450 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
451 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
452 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
453 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
454 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
455 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
456 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
457 else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
458 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
459 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
460 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
461 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
462 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
463 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
464 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
465 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
466 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; }
467 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
469 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
478 int CL_ParticleEffectIndexForName(const char *name)
481 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
482 if (!strcmp(particleeffectname[i], name))
487 const char *CL_ParticleEffectNameForIndex(int i)
489 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
491 return particleeffectname[i];
494 // MUST match effectnameindex_t in client.h
495 static const char *standardeffectnames[EFFECT_TOTAL] =
519 "TE_TEI_BIGEXPLOSION",
535 static void CL_Particles_LoadEffectInfo(const char *customfile)
539 unsigned char *filedata;
540 fs_offset_t filesize;
541 char filename[MAX_QPATH];
542 numparticleeffectinfo = 0;
543 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
544 memset(particleeffectname, 0, sizeof(particleeffectname));
545 for (i = 0;i < EFFECT_TOTAL;i++)
546 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
547 for (filepass = 0;;filepass++)
552 strlcpy(filename, customfile, sizeof(filename));
554 strlcpy(filename, "effectinfo.txt", sizeof(filename));
556 else if (filepass == 1)
558 if (!cl.worldbasename[0] || customfile)
560 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
564 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
567 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
572 static void CL_Particles_LoadEffectInfo_f(cmd_state_t *cmd)
574 CL_Particles_LoadEffectInfo(Cmd_Argc(cmd) > 1 ? Cmd_Argv(cmd, 1) : NULL);
582 void CL_ReadPointFile_f(cmd_state_t *cmd);
583 void CL_Particles_Init (void)
585 Cmd_AddCommand(&cmd_client, "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)");
586 Cmd_AddCommand(&cmd_client, "cl_particles_reloadeffects", CL_Particles_LoadEffectInfo_f, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map) if parameter is given, loads from custom file (no levelname_effectinfo are loaded in this case)");
588 Cvar_RegisterVariable (&cl_particles);
589 Cvar_RegisterVariable (&cl_particles_quality);
590 Cvar_RegisterVariable (&cl_particles_alpha);
591 Cvar_RegisterVariable (&cl_particles_size);
592 Cvar_RegisterVariable (&cl_particles_quake);
593 Cvar_RegisterVariable (&cl_particles_blood);
594 Cvar_RegisterVariable (&cl_particles_blood_alpha);
595 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
596 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
597 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
598 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
599 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
600 Cvar_RegisterVariable (&cl_particles_explosions_shell);
601 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
602 Cvar_RegisterVariable (&cl_particles_rain);
603 Cvar_RegisterVariable (&cl_particles_snow);
604 Cvar_RegisterVariable (&cl_particles_smoke);
605 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
606 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
607 Cvar_RegisterVariable (&cl_particles_sparks);
608 Cvar_RegisterVariable (&cl_particles_bubbles);
609 Cvar_RegisterVariable (&cl_particles_visculling);
610 Cvar_RegisterVariable (&cl_particles_collisions);
611 Cvar_RegisterVariable (&cl_particles_forcetraileffects);
612 Cvar_RegisterVariable (&cl_decals);
613 Cvar_RegisterVariable (&cl_decals_time);
614 Cvar_RegisterVariable (&cl_decals_fadetime);
615 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
616 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
617 Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears);
618 Cvar_RegisterVariable (&cl_decals_models);
619 Cvar_RegisterVariable (&cl_decals_bias);
620 Cvar_RegisterVariable (&cl_decals_max);
623 void CL_Particles_Shutdown (void)
627 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
628 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
630 // list of all 26 parameters:
631 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
632 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
633 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
634 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
635 // palpha - opacity of particle as 0-255 (can be more than 255)
636 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
637 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
638 // pgravity - how much effect gravity has on the particle (0-1)
639 // 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
640 // px,py,pz - starting origin of particle
641 // pvx,pvy,pvz - starting velocity of particle
642 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
643 // blendmode - one of the PBLEND_ values
644 // orientation - one of the PARTICLE_ values
645 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
646 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
647 // stainalpha: opacity of the stain as factor for alpha
648 // stainsize: size of the stain as factor for palpha
649 // angle: base rotation of the particle geometry around its center normal
650 // spin: rotation speed of the particle geometry around its center normal
651 particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, 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, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4])
656 if (!cl_particles.integer)
658 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
659 if (cl.free_particle >= cl.max_particles)
662 lifetime = palpha / min(1, palphafade);
663 part = &cl.particles[cl.free_particle++];
664 if (cl.num_particles < cl.free_particle)
665 cl.num_particles = cl.free_particle;
666 memset(part, 0, sizeof(*part));
667 VectorCopy(sortorigin, part->sortorigin);
668 part->typeindex = ptypeindex;
669 part->blendmode = blendmode;
670 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
672 particletexture_t *tex = &particletexture[ptex];
673 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
674 part->orientation = PARTICLE_VBEAM;
676 part->orientation = PARTICLE_HBEAM;
679 part->orientation = orientation;
680 l2 = (int)lhrandom(0.5, 256.5);
682 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
683 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
684 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
687 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
688 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
689 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
691 part->alpha = palpha;
692 part->alphafade = palphafade;
693 part->staintexnum = staintex;
694 if(staincolor1 >= 0 && staincolor2 >= 0)
696 l2 = (int)lhrandom(0.5, 256.5);
698 if(blendmode == PBLEND_INVMOD)
700 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
701 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
702 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
706 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
707 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
708 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
710 if(r > 0xFF) r = 0xFF;
711 if(g > 0xFF) g = 0xFF;
712 if(b > 0xFF) b = 0xFF;
716 r = part->color[0]; // -1 is shorthand for stain = particle color
720 part->staincolor[0] = r;
721 part->staincolor[1] = g;
722 part->staincolor[2] = b;
723 part->stainalpha = palpha * stainalpha;
724 part->stainsize = psize * stainsize;
727 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
729 part->color[0] *= tint[0];
730 part->color[1] *= tint[1];
731 part->color[2] *= tint[2];
733 part->alpha *= tint[3];
734 part->alphafade *= tint[3];
735 part->stainalpha *= tint[3];
739 part->sizeincrease = psizeincrease;
740 part->gravity = pgravity;
741 part->bounce = pbounce;
742 part->stretch = stretch;
744 part->org[0] = px + originjitter * v[0];
745 part->org[1] = py + originjitter * v[1];
746 part->org[2] = pz + originjitter * v[2];
747 part->vel[0] = pvx + velocityjitter * v[0];
748 part->vel[1] = pvy + velocityjitter * v[1];
749 part->vel[2] = pvz + velocityjitter * v[2];
751 part->airfriction = pairfriction;
752 part->liquidfriction = pliquidfriction;
753 part->die = cl.time + lifetime;
754 part->delayedspawn = cl.time;
755 // part->delayedcollisions = 0;
756 part->qualityreduction = pqualityreduction;
759 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
760 if (part->typeindex == pt_rain)
766 // turn raindrop into simple spark and create delayedspawn splash effect
767 part->typeindex = pt_spark;
769 VectorMA(part->org, lifetime, part->vel, endvec);
770 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK, 0, 0, collision_extendmovelength.value, true, false, NULL, false, false);
771 part->die = cl.time + lifetime * trace.fraction;
772 part2 = CL_NewParticle(endvec, pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1, 1, 1, 0, 0, NULL);
775 part2->delayedspawn = part->die;
776 part2->die += part->die - cl.time;
777 for (i = rand() & 7;i < 10;i++)
779 part2 = CL_NewParticle(endvec, pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
782 part2->delayedspawn = part->die;
783 part2->die += part->die - cl.time;
789 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
791 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
794 VectorMA(part->org, lifetime, part->vel, endvec);
795 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
796 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
803 static void CL_ImmediateBloodStain(particle_t *part)
808 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
809 if (part->staintexnum >= 0 && cl_decals.integer)
811 VectorCopy(part->vel, v);
813 staintex = part->staintexnum;
814 R_DecalSystem_SplatEntities(part->org, v, 1-part->staincolor[0]*(1.0f/255.0f), 1-part->staincolor[1]*(1.0f/255.0f), 1-part->staincolor[2]*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize);
817 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
818 if (part->typeindex == pt_blood && cl_decals.integer)
820 VectorCopy(part->vel, v);
822 staintex = tex_blooddecal[rand()&7];
823 R_DecalSystem_SplatEntities(part->org, v, part->color[0]*(1.0f/255.0f), part->color[1]*(1.0f/255.0f), part->color[2]*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2);
827 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
830 entity_render_t *ent = &cl.entities[hitent].render;
831 unsigned char color[3];
832 if (!cl_decals.integer)
834 if (!ent->allowdecals)
837 l2 = (int)lhrandom(0.5, 256.5);
839 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
840 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
841 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
844 R_DecalSystem_SplatEntities(org, normal, Image_LinearFloatFromsRGB(color[0]), Image_LinearFloatFromsRGB(color[1]), Image_LinearFloatFromsRGB(color[2]), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
846 R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
849 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
856 int besthitent = 0, hitent;
859 for (i = 0;i < 32;i++)
862 VectorMA(org, maxdist, org2, org2);
863 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, 0, 0, collision_extendmovelength.value, true, false, &hitent, false, true);
864 // take the closest trace result that doesn't end up hitting a NOMARKS
865 // surface (sky for example)
866 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
868 bestfrac = trace.fraction;
870 VectorCopy(trace.endpos, bestorg);
871 VectorCopy(trace.plane.normal, bestnormal);
875 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
878 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
879 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
880 static void CL_NewParticlesFromEffectinfo(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, float tintmins[4], float tintmaxs[4], float fade, qboolean wanttrail);
881 static 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, qboolean wanttrail)
884 matrix4x4_t lightmatrix;
887 VectorLerp(originmins, 0.5, originmaxs, center);
888 Matrix4x4_CreateTranslate(&lightmatrix, center[0], center[1], center[2]);
889 if (effectnameindex == EFFECT_SVC_PARTICLE)
891 if (cl_particles.integer)
893 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
895 CL_NewParticlesFromEffectinfo(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
896 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
897 CL_NewParticlesFromEffectinfo(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
900 count *= cl_particles_quality.value;
901 for (;count > 0;count--)
903 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
904 CL_NewParticle(center, pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 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, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
909 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
910 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
911 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
912 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
913 else if (effectnameindex == EFFECT_TE_SPIKE)
915 if (cl_particles_bulletimpacts.integer)
917 if (cl_particles_quake.integer)
919 if (cl_particles_smoke.integer)
920 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
924 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
925 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
926 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
930 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
931 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
933 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
935 if (cl_particles_bulletimpacts.integer)
937 if (cl_particles_quake.integer)
939 if (cl_particles_smoke.integer)
940 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
944 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
945 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
946 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
950 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
951 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
952 CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
954 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
956 if (cl_particles_bulletimpacts.integer)
958 if (cl_particles_quake.integer)
960 if (cl_particles_smoke.integer)
961 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
965 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
966 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
967 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
971 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
972 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
974 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
976 if (cl_particles_bulletimpacts.integer)
978 if (cl_particles_quake.integer)
980 if (cl_particles_smoke.integer)
981 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
985 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
986 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
987 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
991 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
992 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
993 CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
995 else if (effectnameindex == EFFECT_TE_BLOOD)
997 if (!cl_particles_blood.integer)
999 if (cl_particles_quake.integer)
1000 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1003 static double bloodaccumulator = 0;
1004 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1005 //CL_NewParticle(center, pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, NULL);
1006 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1007 for (;bloodaccumulator > 0;bloodaccumulator--)
1009 part = CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 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, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1010 if (immediatebloodstain && part)
1012 immediatebloodstain = false;
1013 CL_ImmediateBloodStain(part);
1018 else if (effectnameindex == EFFECT_TE_SPARK)
1019 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1020 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1022 // plasma scorch mark
1023 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1024 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1025 CL_AllocLightFlash(NULL, &lightmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1027 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1029 if (cl_particles_bulletimpacts.integer)
1031 if (cl_particles_quake.integer)
1032 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1035 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1036 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1037 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1041 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1042 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1044 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1046 if (cl_particles_bulletimpacts.integer)
1048 if (cl_particles_quake.integer)
1049 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1052 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1053 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1054 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1058 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1059 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1060 CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1062 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1064 CL_ParticleExplosion(center);
1065 CL_AllocLightFlash(NULL, &lightmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1067 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1069 CL_ParticleExplosion(center);
1070 CL_AllocLightFlash(NULL, &lightmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1072 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1074 if (cl_particles_quake.integer)
1077 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1080 CL_NewParticle(center, pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1082 CL_NewParticle(center, pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1086 CL_ParticleExplosion(center);
1087 CL_AllocLightFlash(NULL, &lightmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1089 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1090 CL_AllocLightFlash(NULL, &lightmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1091 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1093 count *= cl_particles_quality.value;
1095 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1097 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1099 float i, j, inc, vel;
1102 inc = 8 / cl_particles_quality.value;
1103 for (i = -128;i < 128;i += inc)
1105 for (j = -128;j < 128;j += inc)
1107 dir[0] = j + lhrandom(0, inc);
1108 dir[1] = i + lhrandom(0, inc);
1110 org[0] = center[0] + dir[0];
1111 org[1] = center[1] + dir[1];
1112 org[2] = center[2] + lhrandom(0, 64);
1113 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1114 CL_NewParticle(center, pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1118 else if (effectnameindex == EFFECT_TE_TELEPORT)
1120 float i, j, k, inc, vel;
1123 if (cl_particles_quake.integer)
1124 inc = 4 / cl_particles_quality.value;
1126 inc = 8 / cl_particles_quality.value;
1127 for (i = -16;i < 16;i += inc)
1129 for (j = -16;j < 16;j += inc)
1131 for (k = -24;k < 32;k += inc)
1133 VectorSet(dir, i*8, j*8, k*8);
1134 VectorNormalize(dir);
1135 vel = lhrandom(50, 113);
1136 if (cl_particles_quake.integer)
1137 CL_NewParticle(center, pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 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, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1139 CL_NewParticle(center, pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1143 if (!cl_particles_quake.integer)
1144 CL_NewParticle(center, pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1145 CL_AllocLightFlash(NULL, &lightmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1147 else if (effectnameindex == EFFECT_TE_TEI_G3)
1148 CL_NewParticle(center, 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, false, 0, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1149 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1151 if (cl_particles_smoke.integer)
1153 count *= 0.25f * cl_particles_quality.value;
1155 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1158 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1160 CL_ParticleExplosion(center);
1161 CL_AllocLightFlash(NULL, &lightmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1163 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1166 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1167 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1168 if (cl_particles_smoke.integer)
1169 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1170 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1171 if (cl_particles_sparks.integer)
1172 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1173 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
1174 CL_AllocLightFlash(NULL, &lightmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1176 else if (effectnameindex == EFFECT_EF_FLAME)
1178 if (!spawnparticles)
1180 count *= 300 * cl_particles_quality.value;
1182 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1183 CL_AllocLightFlash(NULL, &lightmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1185 else if (effectnameindex == EFFECT_EF_STARDUST)
1187 if (!spawnparticles)
1189 count *= 200 * cl_particles_quality.value;
1191 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1192 CL_AllocLightFlash(NULL, &lightmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1194 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1198 int smoke, blood, bubbles, r, color, spawnedcount;
1200 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1203 Vector4Set(light, 0, 0, 0, 0);
1205 if (effectnameindex == EFFECT_TR_ROCKET)
1206 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1207 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1209 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1210 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1212 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1214 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1215 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1219 matrix4x4_t traillightmatrix;
1220 Matrix4x4_CreateFromQuakeEntity(&traillightmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1221 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &traillightmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1222 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1226 if (!spawnparticles)
1229 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1232 VectorSubtract(originmaxs, originmins, dir);
1233 len = VectorNormalizeLength(dir);
1237 dec = -ent->persistent.trail_time;
1238 ent->persistent.trail_time += len;
1239 if (ent->persistent.trail_time < 0.01f)
1242 // if we skip out, leave it reset
1243 ent->persistent.trail_time = 0.0f;
1248 // advance into this frame to reach the first puff location
1249 VectorMA(originmins, dec, dir, pos);
1252 smoke = cl_particles.integer && cl_particles_smoke.integer;
1253 blood = cl_particles.integer && cl_particles_blood.integer;
1254 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1255 qd = 1.0f / cl_particles_quality.value;
1258 while (len >= 0 && ++spawnedcount <= 16384)
1263 if (effectnameindex == EFFECT_TR_BLOOD)
1265 if (cl_particles_quake.integer)
1267 color = particlepalette[67 + (rand()&3)];
1268 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1273 CL_NewParticle(center, 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, 1, -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, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1276 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1278 if (cl_particles_quake.integer)
1281 color = particlepalette[67 + (rand()&3)];
1282 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1287 CL_NewParticle(center, 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, 1, -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, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1293 if (effectnameindex == EFFECT_TR_ROCKET)
1295 if (cl_particles_quake.integer)
1298 color = particlepalette[ramp3[r]];
1299 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1303 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1304 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1307 else if (effectnameindex == EFFECT_TR_GRENADE)
1309 if (cl_particles_quake.integer)
1312 color = particlepalette[ramp3[r]];
1313 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1317 CL_NewParticle(center, pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1320 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1322 if (cl_particles_quake.integer)
1325 color = particlepalette[52 + (rand()&7)];
1326 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1327 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1329 else if (gamemode == GAME_GOODVSBAD2)
1332 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1336 color = particlepalette[20 + (rand()&7)];
1337 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1340 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1342 if (cl_particles_quake.integer)
1345 color = particlepalette[230 + (rand()&7)];
1346 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1347 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1351 color = particlepalette[226 + (rand()&7)];
1352 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1355 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1357 if (cl_particles_quake.integer)
1359 color = particlepalette[152 + (rand()&3)];
1360 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1362 else if (gamemode == GAME_GOODVSBAD2)
1365 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1367 else if (gamemode == GAME_PRYDON)
1370 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1373 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1375 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1378 CL_NewParticle(center, 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, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1380 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1383 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1385 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1386 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1390 if (effectnameindex == EFFECT_TR_ROCKET)
1391 CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1392 else if (effectnameindex == EFFECT_TR_GRENADE)
1393 CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1395 // advance to next time and position
1398 VectorMA (pos, dec, dir, pos);
1401 ent->persistent.trail_time = len;
1404 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1407 // this is also called on point effects with spawndlight = true and
1408 // spawnparticles = true
1409 static void CL_NewParticlesFromEffectinfo(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, float tintmins[4], float tintmaxs[4], float fade, qboolean wanttrail)
1411 qboolean found = false;
1413 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1415 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1416 return; // no such effect
1418 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1420 int effectinfoindex;
1423 particleeffectinfo_t *info;
1435 qboolean underwater;
1436 qboolean immediatebloodstain;
1438 float avgtint[4], tint[4], tintlerp;
1439 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1440 VectorLerp(originmins, 0.5, originmaxs, center);
1441 supercontents = CL_PointSuperContents(center);
1442 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1443 VectorSubtract(originmaxs, originmins, traildir);
1444 traillen = VectorLength(traildir);
1445 VectorNormalize(traildir);
1448 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1452 Vector4Set(avgtint, 1, 1, 1, 1);
1454 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1456 if ((info->effectnameindex == effectnameindex) && (info->flags & PARTICLEEFFECT_DEFINED))
1458 qboolean definedastrail = info->trailspacing > 0;
1460 qboolean drawastrail = wanttrail;
1461 if (cl_particles_forcetraileffects.integer)
1462 drawastrail = drawastrail || definedastrail;
1465 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1467 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1470 // spawn a dlight if requested
1471 if (info->lightradiusstart > 0 && spawndlight)
1473 matrix4x4_t tempmatrix;
1475 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1477 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1478 if (info->lighttime > 0 && info->lightradiusfade > 0)
1480 // light flash (explosion, etc)
1481 // called when effect starts
1482 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0]*avgtint[0]*avgtint[3], info->lightcolor[1]*avgtint[1]*avgtint[3], info->lightcolor[2]*avgtint[2]*avgtint[3], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1484 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1487 // called by CL_LinkNetworkEntity
1488 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1489 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1490 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1491 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1492 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, info->lightcubemapnum > 0 ? va(vabuf, sizeof(vabuf), "cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1493 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1497 if (!spawnparticles)
1502 if (info->tex[1] > info->tex[0])
1504 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1505 tex = min(tex, info->tex[1] - 1);
1507 if(info->staintex[0] < 0)
1508 staintex = info->staintex[0];
1511 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1512 staintex = min(staintex, info->staintex[1] - 1);
1514 if (info->particletype == pt_decal)
1516 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1517 AnglesFromVectors(angles, velocity, NULL, false);
1518 AngleVectors(angles, forward, right, up);
1519 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1521 CL_SpawnDecalParticleForPoint(trailpos, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1])*avgtint[3], tex, info->color[0], info->color[1]);
1523 else if (info->orientation == PARTICLE_HBEAM)
1528 AnglesFromVectors(angles, traildir, NULL, false);
1529 AngleVectors(angles, forward, right, up);
1530 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1532 CL_NewParticle(center, 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] + trailpos[0], originmins[1] + trailpos[1], originmins[2] + trailpos[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), 0, 0, tintmins ? avgtint : NULL);
1537 if (!cl_particles.integer)
1539 switch (info->particletype)
1541 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1542 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1543 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1544 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1545 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1546 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1550 cnt = info->countabsolute;
1551 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1552 // if drawastrail is not set, we will
1553 // use the regular cnt-based random
1554 // particle spawning at the center; so
1555 // do NOT apply trailspacing then!
1556 if (drawastrail && definedastrail)
1557 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1560 continue; // nothing to draw
1561 info->particleaccumulator += cnt;
1563 if (drawastrail || definedastrail)
1564 immediatebloodstain = false;
1566 immediatebloodstain =
1567 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1569 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1573 VectorCopy(originmins, trailpos);
1574 trailstep = traillen / cnt;
1578 VectorCopy(center, trailpos);
1584 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1585 AnglesFromVectors(angles, velocity, NULL, false);
1588 AnglesFromVectors(angles, traildir, NULL, false);
1590 AngleVectors(angles, forward, right, up);
1591 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1592 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1593 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1594 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1596 if (info->tex[1] > info->tex[0])
1598 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1599 tex = min(tex, info->tex[1] - 1);
1601 if (!(drawastrail || definedastrail))
1603 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1604 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1605 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1609 tintlerp = lhrandom(0, 1);
1610 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1613 part = CL_NewParticle(center, 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] + velocity[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1] + velocity[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2] + velocity[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), lhrandom(info->rotate[0], info->rotate[1]), lhrandom(info->rotate[2], info->rotate[3]), tintmins ? tint : NULL);
1614 if (immediatebloodstain && part)
1616 immediatebloodstain = false;
1617 CL_ImmediateBloodStain(part);
1620 VectorMA(trailpos, trailstep, traildir, trailpos);
1627 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, wanttrail);
1630 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, float tintmins[4], float tintmaxs[4], float fade)
1632 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1635 void CL_ParticleBox(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, float tintmins[4], float tintmaxs[4], float fade)
1637 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1640 // note: this one ONLY does boxes!
1641 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)
1643 CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1651 void CL_EntityParticles (const entity_t *ent)
1654 vec_t pitch, yaw, dist = 64, beamlength = 16;
1656 static vec3_t avelocities[NUMVERTEXNORMALS];
1657 if (!cl_particles.integer) return;
1658 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1660 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1662 if (!avelocities[0][0])
1663 for (i = 0;i < NUMVERTEXNORMALS;i++)
1664 for (j = 0;j < 3;j++)
1665 avelocities[i][j] = lhrandom(0, 2.55);
1667 for (i = 0;i < NUMVERTEXNORMALS;i++)
1669 yaw = cl.time * avelocities[i][0];
1670 pitch = cl.time * avelocities[i][1];
1671 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1672 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1673 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1674 CL_NewParticle(org, 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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1679 void CL_ReadPointFile_f(cmd_state_t *cmd)
1681 double org[3], leakorg[3];
1684 char *pointfile = NULL, *pointfilepos, *t, tchar;
1685 char name[MAX_QPATH];
1690 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1691 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1694 Con_Printf("Could not open %s\n", name);
1698 Con_Printf("Reading %s...\n", name);
1699 VectorClear(leakorg);
1702 pointfilepos = pointfile;
1703 while (*pointfilepos)
1705 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1710 while (*t && *t != '\n' && *t != '\r')
1714 #if _MSC_VER >= 1400
1715 #define sscanf sscanf_s
1717 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1718 VectorCopy(org, vecorg);
1724 VectorCopy(org, leakorg);
1727 if (cl.num_particles < cl.max_particles - 3)
1730 CL_NewParticle(vecorg, pt_alphastatic, 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, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1733 Mem_Free(pointfile);
1734 VectorCopy(leakorg, vecorg);
1735 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1742 CL_NewParticle(vecorg, 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, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1743 CL_NewParticle(vecorg, 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, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1744 CL_NewParticle(vecorg, 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, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1749 CL_ParseParticleEffect
1751 Parse an effect out of the server message
1754 void CL_ParseParticleEffect (void)
1757 int i, count, msgcount, color;
1759 MSG_ReadVector(&cl_message, org, cls.protocol);
1760 for (i=0 ; i<3 ; i++)
1761 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1762 msgcount = MSG_ReadByte(&cl_message);
1763 color = MSG_ReadByte(&cl_message);
1765 if (msgcount == 255)
1770 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1775 CL_ParticleExplosion
1779 void CL_ParticleExplosion (const vec3_t org)
1785 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1786 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1788 if (cl_particles_quake.integer)
1790 for (i = 0;i < 1024;i++)
1796 color = particlepalette[ramp1[r]];
1797 CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1801 color = particlepalette[ramp2[r]];
1802 CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1808 i = CL_PointSuperContents(org);
1809 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1811 if (cl_particles.integer && cl_particles_bubbles.integer)
1812 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1813 CL_NewParticle(org, 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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1817 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1819 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1826 VectorMA(org, 128, v2, v);
1827 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, 0, 0, collision_extendmovelength.value, true, false, NULL, false, false);
1829 while (k++ < 16 && trace.fraction < 0.1f);
1830 VectorSubtract(trace.endpos, org, v2);
1831 VectorScale(v2, 2.0f, v2);
1832 CL_NewParticle(org, pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
1838 if (cl_particles_explosions_shell.integer)
1839 R_NewExplosion(org);
1844 CL_ParticleExplosion2
1848 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1851 if (!cl_particles.integer) return;
1853 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1855 k = particlepalette[colorStart + (i % colorLength)];
1856 if (cl_particles_quake.integer)
1857 CL_NewParticle(org, pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1859 CL_NewParticle(org, pt_alphastatic, 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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1863 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1866 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1867 if (cl_particles_sparks.integer)
1869 sparkcount *= cl_particles_quality.value;
1870 while(sparkcount-- > 0)
1871 CL_NewParticle(center, pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 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]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
1875 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1878 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1879 if (cl_particles_smoke.integer)
1881 smokecount *= cl_particles_quality.value;
1882 while(smokecount-- > 0)
1883 CL_NewParticle(center, pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 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, smokecount > 0 ? 16 : 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1887 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)
1891 if (!cl_particles.integer) return;
1892 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1894 count = (int)(count * cl_particles_quality.value);
1897 k = particlepalette[colorbase + (rand()&3)];
1898 CL_NewParticle(center, 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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1902 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1905 float minz, maxz, lifetime = 30;
1907 if (!cl_particles.integer) return;
1908 if (dir[2] < 0) // falling
1910 minz = maxs[2] + dir[2] * 0.1;
1913 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1918 maxz = maxs[2] + dir[2] * 0.1;
1920 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1923 count = (int)(count * cl_particles_quality.value);
1928 if (!cl_particles_rain.integer) break;
1929 count *= 4; // ick, this should be in the mod or maps?
1933 k = particlepalette[colorbase + (rand()&3)];
1934 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1935 if (gamemode == GAME_GOODVSBAD2)
1936 CL_NewParticle(org, pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
1938 CL_NewParticle(org, pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
1942 if (!cl_particles_snow.integer) break;
1945 k = particlepalette[colorbase + (rand()&3)];
1946 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1947 if (gamemode == GAME_GOODVSBAD2)
1948 CL_NewParticle(org, pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1950 CL_NewParticle(org, pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1954 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1958 cvar_t r_drawparticles = {CVAR_CLIENT, "r_drawparticles", "1", "enables drawing of particles"};
1959 static cvar_t r_drawparticles_drawdistance = {CVAR_CLIENT | CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1960 static cvar_t r_drawparticles_nearclip_min = {CVAR_CLIENT | CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1961 static cvar_t r_drawparticles_nearclip_max = {CVAR_CLIENT | CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1962 cvar_t r_drawdecals = {CVAR_CLIENT, "r_drawdecals", "1", "enables drawing of decals"};
1963 static cvar_t r_drawdecals_drawdistance = {CVAR_CLIENT | CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1965 #define PARTICLETEXTURESIZE 64
1966 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1968 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1972 dz = 1 - (dx*dx+dy*dy);
1973 if (dz > 0) // it does hit the sphere
1977 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1978 VectorNormalize(normal);
1979 dot = DotProduct(normal, light);
1980 if (dot > 0.5) // interior reflection
1981 f += ((dot * 2) - 1);
1982 else if (dot < -0.5) // exterior reflection
1983 f += ((dot * -2) - 1);
1985 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1986 VectorNormalize(normal);
1987 dot = DotProduct(normal, light);
1988 if (dot > 0.5) // interior reflection
1989 f += ((dot * 2) - 1);
1990 else if (dot < -0.5) // exterior reflection
1991 f += ((dot * -2) - 1);
1993 f += 16; // just to give it a haze so you can see the outline
1994 f = bound(0, f, 255);
1995 return (unsigned char) f;
2001 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
2002 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
2004 *basex = (texnum % particlefontcols) * particlefontcellwidth;
2005 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
2006 *width = particlefontcellwidth;
2007 *height = particlefontcellheight;
2010 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
2012 int basex, basey, w, h, y;
2013 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2014 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2015 Sys_Error("invalid particle texture size for autogenerating");
2016 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2017 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2020 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2023 float cx, cy, dx, dy, f, iradius;
2025 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2026 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2027 iradius = 1.0f / radius;
2028 alpha *= (1.0f / 255.0f);
2029 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2031 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2035 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2040 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2041 d[0] += (int)(f * (blue - d[0]));
2042 d[1] += (int)(f * (green - d[1]));
2043 d[2] += (int)(f * (red - d[2]));
2050 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2053 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2055 data[0] = bound(minb, data[0], maxb);
2056 data[1] = bound(ming, data[1], maxg);
2057 data[2] = bound(minr, data[2], maxr);
2062 static void particletextureinvert(unsigned char *data)
2065 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2067 data[0] = 255 - data[0];
2068 data[1] = 255 - data[1];
2069 data[2] = 255 - data[2];
2073 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2074 static void R_InitBloodTextures (unsigned char *particletexturedata)
2077 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2078 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2081 for (i = 0;i < 8;i++)
2083 memset(data, 255, datasize);
2084 for (k = 0;k < 24;k++)
2085 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2086 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2087 particletextureinvert(data);
2088 setuptex(tex_bloodparticle[i], data, particletexturedata);
2092 for (i = 0;i < 8;i++)
2094 memset(data, 255, datasize);
2096 for (j = 1;j < 10;j++)
2097 for (k = min(j, m - 1);k < m;k++)
2098 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2099 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2100 particletextureinvert(data);
2101 setuptex(tex_blooddecal[i], data, particletexturedata);
2107 //uncomment this to make engine save out particle font to a tga file when run
2108 //#define DUMPPARTICLEFONT
2110 static void R_InitParticleTexture (void)
2112 int x, y, d, i, k, m;
2113 int basex, basey, w, h;
2114 float dx, dy, f, s1, t1, s2, t2;
2117 fs_offset_t filesize;
2118 char texturename[MAX_QPATH];
2121 // a note: decals need to modulate (multiply) the background color to
2122 // properly darken it (stain), and they need to be able to alpha fade,
2123 // this is a very difficult challenge because it means fading to white
2124 // (no change to background) rather than black (darkening everything
2125 // behind the whole decal polygon), and to accomplish this the texture is
2126 // inverted (dark red blood on white background becomes brilliant cyan
2127 // and white on black background) so we can alpha fade it to black, then
2128 // we invert it again during the blendfunc to make it work...
2130 #ifndef DUMPPARTICLEFONT
2131 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false, false);
2134 particlefonttexture = decalskinframe->base;
2135 // TODO maybe allow custom grid size?
2136 particlefontwidth = image_width;
2137 particlefontheight = image_height;
2138 particlefontcellwidth = image_width / 8;
2139 particlefontcellheight = image_height / 8;
2140 particlefontcols = 8;
2141 particlefontrows = 8;
2146 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2147 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2148 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2149 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2150 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2152 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2153 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2154 particlefontcols = 8;
2155 particlefontrows = 8;
2157 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2160 for (i = 0;i < 8;i++)
2162 memset(data, 255, datasize);
2165 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2166 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2168 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2170 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2171 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2173 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2174 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2176 d = (int)(d * (1-(dx*dx+dy*dy)));
2177 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2178 d = bound(0, d, 255);
2179 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2186 setuptex(tex_smoke[i], data, particletexturedata);
2190 memset(data, 255, datasize);
2191 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2193 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2194 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2196 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2197 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2198 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2201 setuptex(tex_rainsplash, data, particletexturedata);
2204 memset(data, 255, datasize);
2205 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2207 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2208 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2210 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2211 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2212 d = bound(0, d, 255);
2213 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2216 setuptex(tex_particle, data, particletexturedata);
2219 memset(data, 255, datasize);
2220 light[0] = 1;light[1] = 1;light[2] = 1;
2221 VectorNormalize(light);
2222 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2224 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2225 // stretch upper half of bubble by +50% and shrink lower half by -50%
2226 // (this gives an elongated teardrop shape)
2228 dy = (dy - 0.5f) * 2.0f;
2230 dy = (dy - 0.5f) / 1.5f;
2231 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2233 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2234 // shrink bubble width to half
2236 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2239 setuptex(tex_raindrop, data, particletexturedata);
2242 memset(data, 255, datasize);
2243 light[0] = 1;light[1] = 1;light[2] = 1;
2244 VectorNormalize(light);
2245 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2247 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2248 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2250 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2251 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2254 setuptex(tex_bubble, data, particletexturedata);
2256 // Blood particles and blood decals
2257 R_InitBloodTextures (particletexturedata);
2260 for (i = 0;i < 8;i++)
2262 memset(data, 255, datasize);
2263 for (k = 0;k < 12;k++)
2264 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2265 for (k = 0;k < 3;k++)
2266 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2267 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2268 particletextureinvert(data);
2269 setuptex(tex_bulletdecal[i], data, particletexturedata);
2272 #ifdef DUMPPARTICLEFONT
2273 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2276 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, 0, 0, 0, false);
2277 particlefonttexture = decalskinframe->base;
2279 Mem_Free(particletexturedata);
2284 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2286 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2287 particletexture[i].texture = particlefonttexture;
2288 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2289 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2290 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2291 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2294 #ifndef DUMPPARTICLEFONT
2295 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2296 if (!particletexture[tex_beam].texture)
2299 unsigned char noise3[64][64], data2[64][16][4];
2301 fractalnoise(&noise3[0][0], 64, 4);
2303 for (y = 0;y < 64;y++)
2305 dy = (y - 0.5f*64) / (64*0.5f-1);
2306 for (x = 0;x < 16;x++)
2308 dx = (x - 0.5f*16) / (16*0.5f-2);
2309 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2310 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2311 data2[y][x][3] = 255;
2315 #ifdef DUMPPARTICLEFONT
2316 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2318 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2320 particletexture[tex_beam].s1 = 0;
2321 particletexture[tex_beam].t1 = 0;
2322 particletexture[tex_beam].s2 = 1;
2323 particletexture[tex_beam].t2 = 1;
2325 // now load an texcoord/texture override file
2326 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2333 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2335 if(!strcmp(com_token, "\n"))
2336 continue; // empty line
2337 i = atoi(com_token);
2345 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2347 strlcpy(texturename, com_token, sizeof(texturename));
2348 s1 = atof(com_token);
2349 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2352 t1 = atof(com_token);
2353 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2355 s2 = atof(com_token);
2356 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2358 t2 = atof(com_token);
2359 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2360 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2361 strlcpy(texturename, com_token, sizeof(texturename));
2368 if (!texturename[0])
2370 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2373 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2375 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2378 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, true); // note: this loads as sRGB if sRGB is active!
2379 particletexture[i].texture = sf->base;
2380 particletexture[i].s1 = s1;
2381 particletexture[i].t1 = t1;
2382 particletexture[i].s2 = s2;
2383 particletexture[i].t2 = t2;
2389 static void r_part_start(void)
2392 // generate particlepalette for convenience from the main one
2393 for (i = 0;i < 256;i++)
2394 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2395 particletexturepool = R_AllocTexturePool();
2396 R_InitParticleTexture ();
2397 CL_Particles_LoadEffectInfo(NULL);
2400 static void r_part_shutdown(void)
2402 R_FreeTexturePool(&particletexturepool);
2405 static void r_part_newmap(void)
2408 R_SkinFrame_MarkUsed(decalskinframe);
2409 CL_Particles_LoadEffectInfo(NULL);
2412 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2413 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2415 void R_Particles_Init (void)
2418 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2420 particle_elements[i*6+0] = i*4+0;
2421 particle_elements[i*6+1] = i*4+1;
2422 particle_elements[i*6+2] = i*4+2;
2423 particle_elements[i*6+3] = i*4+0;
2424 particle_elements[i*6+4] = i*4+2;
2425 particle_elements[i*6+5] = i*4+3;
2428 Cvar_RegisterVariable(&r_drawparticles);
2429 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2430 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2431 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2432 Cvar_RegisterVariable(&r_drawdecals);
2433 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2434 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2437 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2439 vec3_t vecorg, vecvel, baseright, baseup;
2440 int surfacelistindex;
2441 int batchstart, batchcount;
2442 const particle_t *p;
2444 rtexture_t *texture;
2445 float *v3f, *t2f, *c4f;
2446 particletexture_t *tex;
2447 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2448 // float ambient[3], diffuse[3], diffusenormal[3];
2449 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2450 vec4_t colormultiplier;
2451 float minparticledist_start, minparticledist_end;
2454 RSurf_ActiveModelEntity(r_refdef.scene.worldentity, false, false, false);
2456 Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f));
2458 r_refdef.stats[r_stat_particles] += numsurfaces;
2459 // R_Mesh_ResetTextureState();
2460 GL_DepthMask(false);
2461 GL_DepthRange(0, 1);
2462 GL_PolygonOffset(0, 0);
2464 GL_CullFace(GL_NONE);
2466 spintime = r_refdef.scene.time;
2468 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2469 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2470 dofade = (minparticledist_start < minparticledist_end);
2472 // first generate all the vertices at once
2473 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2475 p = cl.particles + surfacelist[surfacelistindex];
2477 blendmode = (pblend_t)p->blendmode;
2479 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2480 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2481 alpha = palpha * colormultiplier[3];
2482 // ensure alpha multiplier saturates properly
2488 case PBLEND_INVALID:
2490 // additive and modulate can just fade out in fog (this is correct)
2491 if (r_refdef.fogenabled)
2492 alpha *= RSurf_FogVertex(p->org);
2493 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2494 alpha *= 1.0f / 256.0f;
2495 c4f[0] = p->color[0] * alpha;
2496 c4f[1] = p->color[1] * alpha;
2497 c4f[2] = p->color[2] * alpha;
2501 // additive and modulate can just fade out in fog (this is correct)
2502 if (r_refdef.fogenabled)
2503 alpha *= RSurf_FogVertex(p->org);
2504 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2505 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2506 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2507 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2511 c4f[0] = p->color[0] * colormultiplier[0];
2512 c4f[1] = p->color[1] * colormultiplier[1];
2513 c4f[2] = p->color[2] * colormultiplier[2];
2515 // note: lighting is not cheap!
2516 if (particletype[p->typeindex].lighting)
2518 float a[3], c[3], dir[3];
2519 vecorg[0] = p->org[0];
2520 vecorg[1] = p->org[1];
2521 vecorg[2] = p->org[2];
2522 R_CompleteLightPoint(a, c, dir, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT, r_refdef.scene.lightmapintensity, r_refdef.scene.ambientintensity);
2523 c4f[0] = p->color[0] * colormultiplier[0] * (a[0] + 0.25f * c[0]);
2524 c4f[1] = p->color[1] * colormultiplier[1] * (a[1] + 0.25f * c[1]);
2525 c4f[2] = p->color[2] * colormultiplier[2] * (a[2] + 0.25f * c[2]);
2527 // mix in the fog color
2528 if (r_refdef.fogenabled)
2530 fog = RSurf_FogVertex(p->org);
2532 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2533 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2534 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2536 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2537 VectorScale(c4f, alpha, c4f);
2540 // copy the color into the other three vertices
2541 Vector4Copy(c4f, c4f + 4);
2542 Vector4Copy(c4f, c4f + 8);
2543 Vector4Copy(c4f, c4f + 12);
2545 size = p->size * cl_particles_size.value;
2546 tex = &particletexture[p->texnum];
2547 switch(p->orientation)
2549 // case PARTICLE_INVALID:
2550 case PARTICLE_BILLBOARD:
2551 if (p->angle + p->spin)
2553 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2554 spinsin = sin(spinrad) * size;
2555 spincos = cos(spinrad) * size;
2556 spinm1 = -p->stretch * spincos;
2559 spinm4 = -p->stretch * spincos;
2560 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2561 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2565 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2566 VectorScale(r_refdef.view.up, size, up);
2569 v3f[ 0] = p->org[0] - right[0] - up[0];
2570 v3f[ 1] = p->org[1] - right[1] - up[1];
2571 v3f[ 2] = p->org[2] - right[2] - up[2];
2572 v3f[ 3] = p->org[0] - right[0] + up[0];
2573 v3f[ 4] = p->org[1] - right[1] + up[1];
2574 v3f[ 5] = p->org[2] - right[2] + up[2];
2575 v3f[ 6] = p->org[0] + right[0] + up[0];
2576 v3f[ 7] = p->org[1] + right[1] + up[1];
2577 v3f[ 8] = p->org[2] + right[2] + up[2];
2578 v3f[ 9] = p->org[0] + right[0] - up[0];
2579 v3f[10] = p->org[1] + right[1] - up[1];
2580 v3f[11] = p->org[2] + right[2] - up[2];
2581 t2f[0] = tex->s1;t2f[1] = tex->t2;
2582 t2f[2] = tex->s1;t2f[3] = tex->t1;
2583 t2f[4] = tex->s2;t2f[5] = tex->t1;
2584 t2f[6] = tex->s2;t2f[7] = tex->t2;
2586 case PARTICLE_ORIENTED_DOUBLESIDED:
2587 vecvel[0] = p->vel[0];
2588 vecvel[1] = p->vel[1];
2589 vecvel[2] = p->vel[2];
2590 VectorVectors(vecvel, baseright, baseup);
2591 if (p->angle + p->spin)
2593 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2594 spinsin = sin(spinrad) * size;
2595 spincos = cos(spinrad) * size;
2596 spinm1 = p->stretch * spincos;
2599 spinm4 = p->stretch * spincos;
2600 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2601 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2605 VectorScale(baseright, size * p->stretch, right);
2606 VectorScale(baseup, size, up);
2608 v3f[ 0] = p->org[0] - right[0] - up[0];
2609 v3f[ 1] = p->org[1] - right[1] - up[1];
2610 v3f[ 2] = p->org[2] - right[2] - up[2];
2611 v3f[ 3] = p->org[0] - right[0] + up[0];
2612 v3f[ 4] = p->org[1] - right[1] + up[1];
2613 v3f[ 5] = p->org[2] - right[2] + up[2];
2614 v3f[ 6] = p->org[0] + right[0] + up[0];
2615 v3f[ 7] = p->org[1] + right[1] + up[1];
2616 v3f[ 8] = p->org[2] + right[2] + up[2];
2617 v3f[ 9] = p->org[0] + right[0] - up[0];
2618 v3f[10] = p->org[1] + right[1] - up[1];
2619 v3f[11] = p->org[2] + right[2] - up[2];
2620 t2f[0] = tex->s1;t2f[1] = tex->t2;
2621 t2f[2] = tex->s1;t2f[3] = tex->t1;
2622 t2f[4] = tex->s2;t2f[5] = tex->t1;
2623 t2f[6] = tex->s2;t2f[7] = tex->t2;
2625 case PARTICLE_SPARK:
2626 len = VectorLength(p->vel);
2627 VectorNormalize2(p->vel, up);
2628 lenfactor = p->stretch * 0.04 * len;
2629 if(lenfactor < size * 0.5)
2630 lenfactor = size * 0.5;
2631 VectorMA(p->org, -lenfactor, up, v);
2632 VectorMA(p->org, lenfactor, up, up2);
2633 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2634 t2f[0] = tex->s1;t2f[1] = tex->t2;
2635 t2f[2] = tex->s1;t2f[3] = tex->t1;
2636 t2f[4] = tex->s2;t2f[5] = tex->t1;
2637 t2f[6] = tex->s2;t2f[7] = tex->t2;
2639 case PARTICLE_VBEAM:
2640 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2641 VectorSubtract(p->vel, p->org, up);
2642 VectorNormalize(up);
2643 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2644 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2645 t2f[0] = tex->s2;t2f[1] = v[0];
2646 t2f[2] = tex->s1;t2f[3] = v[0];
2647 t2f[4] = tex->s1;t2f[5] = v[1];
2648 t2f[6] = tex->s2;t2f[7] = v[1];
2650 case PARTICLE_HBEAM:
2651 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2652 VectorSubtract(p->vel, p->org, up);
2653 VectorNormalize(up);
2654 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2655 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2656 t2f[0] = v[0];t2f[1] = tex->t1;
2657 t2f[2] = v[0];t2f[3] = tex->t2;
2658 t2f[4] = v[1];t2f[5] = tex->t2;
2659 t2f[6] = v[1];t2f[7] = tex->t1;
2662 if (r_showparticleedges.integer)
2664 R_DebugLine(v3f, v3f + 3);
2665 R_DebugLine(v3f + 3, v3f + 6);
2666 R_DebugLine(v3f + 6, v3f + 9);
2667 R_DebugLine(v3f + 9, v3f);
2671 // now render batches of particles based on blendmode and texture
2672 blendmode = PBLEND_INVALID;
2676 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2677 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2679 p = cl.particles + surfacelist[surfacelistindex];
2681 if (texture != particletexture[p->texnum].texture)
2683 texture = particletexture[p->texnum].texture;
2684 R_SetupShader_Generic(texture, false, false, false);
2687 if (p->blendmode == PBLEND_INVMOD)
2689 // inverse modulate blend - group these
2690 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2691 // iterate until we find a change in settings
2692 batchstart = surfacelistindex++;
2693 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2695 p = cl.particles + surfacelist[surfacelistindex];
2696 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2702 // additive or alpha blend - group these
2703 // (we can group these because we premultiplied the texture alpha)
2704 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2705 // iterate until we find a change in settings
2706 batchstart = surfacelistindex++;
2707 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2709 p = cl.particles + surfacelist[surfacelistindex];
2710 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2715 batchcount = surfacelistindex - batchstart;
2716 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2720 void R_DrawParticles (void)
2723 int drawparticles = r_drawparticles.integer;
2724 float minparticledist_start;
2726 float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2732 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2733 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2735 // LadyHavoc: early out conditions
2736 if (!cl.num_particles)
2739 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2740 gravity = frametime * cl.movevars_gravity;
2741 update = frametime > 0;
2742 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2743 drawdist2 = drawdist2*drawdist2;
2745 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2749 if (cl.free_particle > i)
2750 cl.free_particle = i;
2756 if (p->delayedspawn > cl.time)
2759 p->size += p->sizeincrease * frametime;
2760 p->alpha -= p->alphafade * frametime;
2762 if (p->alpha <= 0 || p->die <= cl.time)
2765 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2767 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2769 if (p->typeindex == pt_blood)
2770 p->size += frametime * 8;
2772 p->vel[2] -= p->gravity * gravity;
2773 f = 1.0f - min(p->liquidfriction * frametime, 1);
2774 VectorScale(p->vel, f, p->vel);
2778 p->vel[2] -= p->gravity * gravity;
2781 f = 1.0f - min(p->airfriction * frametime, 1);
2782 VectorScale(p->vel, f, p->vel);
2786 VectorCopy(p->org, oldorg);
2787 VectorMA(p->org, frametime, p->vel, p->org);
2788 // if (p->bounce && cl.time >= p->delayedcollisions)
2789 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2791 trace = CL_TraceLine(oldorg, p->org, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), 0, 0, collision_extendmovelength.value, true, false, &hitent, false, false);
2792 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2793 // or if the trace hit something flagged as NOIMPACT
2794 // then remove the particle
2795 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2797 VectorCopy(trace.endpos, p->org);
2798 // react if the particle hit something
2799 if (trace.fraction < 1)
2801 VectorCopy(trace.endpos, p->org);
2803 if (p->staintexnum >= 0)
2805 // blood - splash on solid
2806 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2809 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2810 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2811 if (cl_decals.integer)
2813 // create a decal for the blood splat
2814 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2815 if (cl_decals_newsystem_bloodsmears.integer)
2817 VectorCopy(p->vel, decaldir);
2818 VectorNormalize(decaldir);
2821 VectorCopy(trace.plane.normal, decaldir);
2822 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2827 if (p->typeindex == pt_blood)
2829 // blood - splash on solid
2830 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2832 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2834 R_Stain(p->org, 16, 64, 16, 16, (int)(p->alpha * p->size * (1.0f / 80.0f)), 64, 32, 32, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2835 if (cl_decals.integer)
2837 // create a decal for the blood splat
2838 if (cl_decals_newsystem_bloodsmears.integer)
2840 VectorCopy(p->vel, decaldir);
2841 VectorNormalize(decaldir);
2844 VectorCopy(trace.plane.normal, decaldir);
2845 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768);
2850 else if (p->bounce < 0)
2852 // bounce -1 means remove on impact
2857 // anything else - bounce off solid
2858 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2859 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2864 if (VectorLength2(p->vel) < 0.03)
2866 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
2868 VectorClear(p->vel);
2872 if (p->typeindex != pt_static)
2874 switch (p->typeindex)
2876 case pt_entityparticle:
2877 // particle that removes itself after one rendered frame
2884 a = CL_PointSuperContents(p->org);
2885 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2889 a = CL_PointSuperContents(p->org);
2890 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2894 a = CL_PointSuperContents(p->org);
2895 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
2899 if (cl.time > p->time2)
2902 p->time2 = cl.time + (rand() & 3) * 0.1;
2903 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2904 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2906 a = CL_PointSuperContents(p->org);
2907 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
2915 else if (p->delayedspawn > cl.time)
2919 // don't render particles too close to the view (they chew fillrate)
2920 // also don't render particles behind the view (useless)
2921 // further checks to cull to the frustum would be too slow here
2922 switch(p->typeindex)
2925 // beams have no culling
2926 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2929 if(cl_particles_visculling.integer)
2930 if (!r_refdef.viewcache.world_novis)
2931 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2933 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2935 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2938 // anything else just has to be in front of the viewer and visible at this distance
2939 if (!r_refdef.view.useperspective || (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size)))
2940 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2947 if (cl.free_particle > i)
2948 cl.free_particle = i;
2951 // reduce cl.num_particles if possible
2952 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2955 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2957 particle_t *oldparticles = cl.particles;
2958 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2959 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2960 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2961 Mem_Free(oldparticles);