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_SAVE, "cl_particles", "1", "enables particle effects"};
278 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
279 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
280 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
281 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
282 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
283 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
284 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
285 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
286 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
287 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
288 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
289 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
290 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
291 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
292 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
293 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
294 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
295 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
296 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
297 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
298 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
299 cvar_t cl_particles_collisions = {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 = {0, "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_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
302 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
303 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
304 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
305 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
306 cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
307 cvar_t cl_decals_newsystem_immediatebloodstain = {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"};
308 cvar_t cl_decals_newsystem_bloodsmears = {CVAR_SAVE, "cl_decals_newsystem_bloodsmears", "1", "enable use of particle velocity as decal projection direction rather than surface normal"};
309 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
310 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
311 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
314 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
320 particleeffectinfo_t *info = NULL;
321 const char *text = textstart;
323 for (linenumber = 1;;linenumber++)
326 for (arrayindex = 0;arrayindex < 16;arrayindex++)
327 argv[arrayindex][0] = 0;
330 if (!COM_ParseToken_Simple(&text, true, false, true))
332 if (!strcmp(com_token, "\n"))
336 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
342 #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;}
343 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
344 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
345 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
346 #define readfloat(var) checkparms(2);var = atof(argv[1])
347 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
348 if (!strcmp(argv[0], "effect"))
352 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
354 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
357 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
359 if (particleeffectname[effectnameindex][0])
361 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
366 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
370 // if we run out of names, abort
371 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
373 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
376 for(i = 0; i < numparticleeffectinfo; ++i)
378 info = particleeffectinfo + i;
379 if(!(info->flags & PARTICLEEFFECT_DEFINED))
380 if(info->effectnameindex == effectnameindex)
383 if(i < numparticleeffectinfo)
385 info = particleeffectinfo + numparticleeffectinfo++;
386 // copy entire info from baseline, then fix up the nameindex
387 *info = baselineparticleeffectinfo;
388 info->effectnameindex = effectnameindex;
391 else if (info == NULL)
393 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
397 info->flags |= PARTICLEEFFECT_DEFINED;
398 if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
399 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
400 else if (!strcmp(argv[0], "type"))
403 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
404 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
405 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
406 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
407 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
408 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
409 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
410 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
411 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
412 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
413 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
414 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
415 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
416 info->blendmode = particletype[info->particletype].blendmode;
417 info->orientation = particletype[info->particletype].orientation;
419 else if (!strcmp(argv[0], "blend"))
422 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
423 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
424 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
425 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
427 else if (!strcmp(argv[0], "orientation"))
430 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
431 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
432 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
433 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
434 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
436 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
437 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
438 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
439 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
440 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
441 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
442 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
443 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
444 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
445 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
446 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
447 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
448 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
449 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
450 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
451 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
452 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
453 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
454 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
455 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
456 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
457 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
458 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
459 else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
460 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
461 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
462 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
463 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
464 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
465 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
466 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
467 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
468 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; }
469 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
471 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
480 int CL_ParticleEffectIndexForName(const char *name)
483 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
484 if (!strcmp(particleeffectname[i], name))
489 const char *CL_ParticleEffectNameForIndex(int i)
491 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
493 return particleeffectname[i];
496 // MUST match effectnameindex_t in client.h
497 static const char *standardeffectnames[EFFECT_TOTAL] =
521 "TE_TEI_BIGEXPLOSION",
537 static void CL_Particles_LoadEffectInfo(const char *customfile)
541 unsigned char *filedata;
542 fs_offset_t filesize;
543 char filename[MAX_QPATH];
544 numparticleeffectinfo = 0;
545 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
546 memset(particleeffectname, 0, sizeof(particleeffectname));
547 for (i = 0;i < EFFECT_TOTAL;i++)
548 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
549 for (filepass = 0;;filepass++)
554 strlcpy(filename, customfile, sizeof(filename));
556 strlcpy(filename, "effectinfo.txt", sizeof(filename));
558 else if (filepass == 1)
560 if (!cl.worldbasename[0] || customfile)
562 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
566 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
569 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
574 static void CL_Particles_LoadEffectInfo_f(void)
576 CL_Particles_LoadEffectInfo(Cmd_Argc() > 1 ? Cmd_Argv(1) : NULL);
584 void CL_ReadPointFile_f (void);
585 void CL_Particles_Init (void)
587 Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
588 Cmd_AddCommand ("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)");
590 Cvar_RegisterVariable (&cl_particles);
591 Cvar_RegisterVariable (&cl_particles_quality);
592 Cvar_RegisterVariable (&cl_particles_alpha);
593 Cvar_RegisterVariable (&cl_particles_size);
594 Cvar_RegisterVariable (&cl_particles_quake);
595 Cvar_RegisterVariable (&cl_particles_blood);
596 Cvar_RegisterVariable (&cl_particles_blood_alpha);
597 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
598 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
599 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
600 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
601 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
602 Cvar_RegisterVariable (&cl_particles_explosions_shell);
603 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
604 Cvar_RegisterVariable (&cl_particles_rain);
605 Cvar_RegisterVariable (&cl_particles_snow);
606 Cvar_RegisterVariable (&cl_particles_smoke);
607 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
608 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
609 Cvar_RegisterVariable (&cl_particles_sparks);
610 Cvar_RegisterVariable (&cl_particles_bubbles);
611 Cvar_RegisterVariable (&cl_particles_visculling);
612 Cvar_RegisterVariable (&cl_particles_collisions);
613 Cvar_RegisterVariable (&cl_particles_forcetraileffects);
614 Cvar_RegisterVariable (&cl_decals);
615 Cvar_RegisterVariable (&cl_decals_visculling);
616 Cvar_RegisterVariable (&cl_decals_time);
617 Cvar_RegisterVariable (&cl_decals_fadetime);
618 Cvar_RegisterVariable (&cl_decals_newsystem);
619 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
620 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
621 Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears);
622 Cvar_RegisterVariable (&cl_decals_models);
623 Cvar_RegisterVariable (&cl_decals_bias);
624 Cvar_RegisterVariable (&cl_decals_max);
627 void CL_Particles_Shutdown (void)
631 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
632 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
634 // list of all 26 parameters:
635 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
636 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
637 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
638 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
639 // palpha - opacity of particle as 0-255 (can be more than 255)
640 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
641 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
642 // pgravity - how much effect gravity has on the particle (0-1)
643 // 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
644 // px,py,pz - starting origin of particle
645 // pvx,pvy,pvz - starting velocity of particle
646 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
647 // blendmode - one of the PBLEND_ values
648 // orientation - one of the PARTICLE_ values
649 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
650 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
651 // stainalpha: opacity of the stain as factor for alpha
652 // stainsize: size of the stain as factor for palpha
653 // angle: base rotation of the particle geometry around its center normal
654 // spin: rotation speed of the particle geometry around its center normal
655 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])
660 if (!cl_particles.integer)
662 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
663 if (cl.free_particle >= cl.max_particles)
666 lifetime = palpha / min(1, palphafade);
667 part = &cl.particles[cl.free_particle++];
668 if (cl.num_particles < cl.free_particle)
669 cl.num_particles = cl.free_particle;
670 memset(part, 0, sizeof(*part));
671 VectorCopy(sortorigin, part->sortorigin);
672 part->typeindex = ptypeindex;
673 part->blendmode = blendmode;
674 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
676 particletexture_t *tex = &particletexture[ptex];
677 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
678 part->orientation = PARTICLE_VBEAM;
680 part->orientation = PARTICLE_HBEAM;
683 part->orientation = orientation;
684 l2 = (int)lhrandom(0.5, 256.5);
686 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
687 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
688 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
691 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
692 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
693 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
695 part->alpha = palpha;
696 part->alphafade = palphafade;
697 part->staintexnum = staintex;
698 if(staincolor1 >= 0 && staincolor2 >= 0)
700 l2 = (int)lhrandom(0.5, 256.5);
702 if(blendmode == PBLEND_INVMOD)
704 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
705 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
706 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
710 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
711 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
712 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
714 if(r > 0xFF) r = 0xFF;
715 if(g > 0xFF) g = 0xFF;
716 if(b > 0xFF) b = 0xFF;
720 r = part->color[0]; // -1 is shorthand for stain = particle color
724 part->staincolor[0] = r;
725 part->staincolor[1] = g;
726 part->staincolor[2] = b;
727 part->stainalpha = palpha * stainalpha;
728 part->stainsize = psize * stainsize;
731 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
733 part->color[0] *= tint[0];
734 part->color[1] *= tint[1];
735 part->color[2] *= tint[2];
737 part->alpha *= tint[3];
738 part->alphafade *= tint[3];
739 part->stainalpha *= tint[3];
743 part->sizeincrease = psizeincrease;
744 part->gravity = pgravity;
745 part->bounce = pbounce;
746 part->stretch = stretch;
748 part->org[0] = px + originjitter * v[0];
749 part->org[1] = py + originjitter * v[1];
750 part->org[2] = pz + originjitter * v[2];
751 part->vel[0] = pvx + velocityjitter * v[0];
752 part->vel[1] = pvy + velocityjitter * v[1];
753 part->vel[2] = pvz + velocityjitter * v[2];
755 part->airfriction = pairfriction;
756 part->liquidfriction = pliquidfriction;
757 part->die = cl.time + lifetime;
758 part->delayedspawn = cl.time;
759 // part->delayedcollisions = 0;
760 part->qualityreduction = pqualityreduction;
763 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
764 if (part->typeindex == pt_rain)
768 float lifetime = part->die - cl.time;
771 // turn raindrop into simple spark and create delayedspawn splash effect
772 part->typeindex = pt_spark;
774 VectorMA(part->org, lifetime, part->vel, endvec);
775 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, collision_extendmovelength.value, true, false, NULL, false, false);
776 part->die = cl.time + lifetime * trace.fraction;
777 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);
780 part2->delayedspawn = part->die;
781 part2->die += part->die - cl.time;
782 for (i = rand() & 7;i < 10;i++)
784 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);
787 part2->delayedspawn = part->die;
788 part2->die += part->die - cl.time;
794 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
796 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
799 VectorMA(part->org, lifetime, part->vel, endvec);
800 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
801 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
808 static void CL_ImmediateBloodStain(particle_t *part)
813 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
814 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
816 VectorCopy(part->vel, v);
818 staintex = part->staintexnum;
819 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);
822 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
823 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
825 VectorCopy(part->vel, v);
827 staintex = tex_blooddecal[rand()&7];
828 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);
832 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
836 entity_render_t *ent = &cl.entities[hitent].render;
837 unsigned char color[3];
838 if (!cl_decals.integer)
840 if (!ent->allowdecals)
843 l2 = (int)lhrandom(0.5, 256.5);
845 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
846 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
847 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
849 if (cl_decals_newsystem.integer)
852 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);
854 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);
858 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
859 if (cl.free_decal >= cl.max_decals)
861 decal = &cl.decals[cl.free_decal++];
862 if (cl.num_decals < cl.free_decal)
863 cl.num_decals = cl.free_decal;
864 memset(decal, 0, sizeof(*decal));
865 decal->decalsequence = cl.decalsequence++;
866 decal->typeindex = pt_decal;
867 decal->texnum = texnum;
868 VectorMA(org, cl_decals_bias.value, normal, decal->org);
869 VectorCopy(normal, decal->normal);
871 decal->alpha = alpha;
872 decal->time2 = cl.time;
873 decal->color[0] = color[0];
874 decal->color[1] = color[1];
875 decal->color[2] = color[2];
878 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
879 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
880 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
882 decal->owner = hitent;
883 decal->clusterindex = -1000; // no vis culling unless we're sure
886 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
887 decal->ownermodel = cl.entities[decal->owner].render.model;
888 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
889 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
893 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
895 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
897 decal->clusterindex = leaf->clusterindex;
902 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
909 int besthitent = 0, hitent;
912 for (i = 0;i < 32;i++)
915 VectorMA(org, maxdist, org2, org2);
916 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, collision_extendmovelength.value, true, false, &hitent, false, true);
917 // take the closest trace result that doesn't end up hitting a NOMARKS
918 // surface (sky for example)
919 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
921 bestfrac = trace.fraction;
923 VectorCopy(trace.endpos, bestorg);
924 VectorCopy(trace.plane.normal, bestnormal);
928 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
931 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
932 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
933 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);
934 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)
937 matrix4x4_t tempmatrix;
940 VectorLerp(originmins, 0.5, originmaxs, center);
941 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
942 if (effectnameindex == EFFECT_SVC_PARTICLE)
944 if (cl_particles.integer)
946 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
948 CL_NewParticlesFromEffectinfo(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
949 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
950 CL_NewParticlesFromEffectinfo(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
953 count *= cl_particles_quality.value;
954 for (;count > 0;count--)
956 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
957 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);
962 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
963 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
964 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
965 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
966 else if (effectnameindex == EFFECT_TE_SPIKE)
968 if (cl_particles_bulletimpacts.integer)
970 if (cl_particles_quake.integer)
972 if (cl_particles_smoke.integer)
973 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
977 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
978 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
979 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);
983 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
984 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
986 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
988 if (cl_particles_bulletimpacts.integer)
990 if (cl_particles_quake.integer)
992 if (cl_particles_smoke.integer)
993 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
997 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
998 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
999 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);
1003 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1004 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1005 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1007 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
1009 if (cl_particles_bulletimpacts.integer)
1011 if (cl_particles_quake.integer)
1013 if (cl_particles_smoke.integer)
1014 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1018 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1019 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1020 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);
1024 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1025 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1027 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1029 if (cl_particles_bulletimpacts.integer)
1031 if (cl_particles_quake.integer)
1033 if (cl_particles_smoke.integer)
1034 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1038 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1039 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1040 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);
1044 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1045 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1046 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1048 else if (effectnameindex == EFFECT_TE_BLOOD)
1050 if (!cl_particles_blood.integer)
1052 if (cl_particles_quake.integer)
1053 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1056 static double bloodaccumulator = 0;
1057 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1058 //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);
1059 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1060 for (;bloodaccumulator > 0;bloodaccumulator--)
1062 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);
1063 if (immediatebloodstain && part)
1065 immediatebloodstain = false;
1066 CL_ImmediateBloodStain(part);
1071 else if (effectnameindex == EFFECT_TE_SPARK)
1072 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1073 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1075 // plasma scorch mark
1076 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1077 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1078 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1080 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1082 if (cl_particles_bulletimpacts.integer)
1084 if (cl_particles_quake.integer)
1085 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1088 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1089 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1090 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);
1094 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1095 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1097 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1099 if (cl_particles_bulletimpacts.integer)
1101 if (cl_particles_quake.integer)
1102 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1105 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1106 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1107 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);
1111 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1112 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1113 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1115 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1117 CL_ParticleExplosion(center);
1118 CL_AllocLightFlash(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1120 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1122 CL_ParticleExplosion(center);
1123 CL_AllocLightFlash(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1125 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1127 if (cl_particles_quake.integer)
1130 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1133 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);
1135 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);
1139 CL_ParticleExplosion(center);
1140 CL_AllocLightFlash(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1142 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1143 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1144 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1146 count *= cl_particles_quality.value;
1148 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);
1150 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1152 float i, j, inc, vel;
1155 inc = 8 / cl_particles_quality.value;
1156 for (i = -128;i < 128;i += inc)
1158 for (j = -128;j < 128;j += inc)
1160 dir[0] = j + lhrandom(0, inc);
1161 dir[1] = i + lhrandom(0, inc);
1163 org[0] = center[0] + dir[0];
1164 org[1] = center[1] + dir[1];
1165 org[2] = center[2] + lhrandom(0, 64);
1166 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1167 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);
1171 else if (effectnameindex == EFFECT_TE_TELEPORT)
1173 float i, j, k, inc, vel;
1176 if (cl_particles_quake.integer)
1177 inc = 4 / cl_particles_quality.value;
1179 inc = 8 / cl_particles_quality.value;
1180 for (i = -16;i < 16;i += inc)
1182 for (j = -16;j < 16;j += inc)
1184 for (k = -24;k < 32;k += inc)
1186 VectorSet(dir, i*8, j*8, k*8);
1187 VectorNormalize(dir);
1188 vel = lhrandom(50, 113);
1189 if (cl_particles_quake.integer)
1190 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);
1192 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);
1196 if (!cl_particles_quake.integer)
1197 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);
1198 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1200 else if (effectnameindex == EFFECT_TE_TEI_G3)
1201 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);
1202 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1204 if (cl_particles_smoke.integer)
1206 count *= 0.25f * cl_particles_quality.value;
1208 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);
1211 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1213 CL_ParticleExplosion(center);
1214 CL_AllocLightFlash(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1216 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1219 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1220 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1221 if (cl_particles_smoke.integer)
1222 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1223 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);
1224 if (cl_particles_sparks.integer)
1225 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1226 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);
1227 CL_AllocLightFlash(NULL, &tempmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1229 else if (effectnameindex == EFFECT_EF_FLAME)
1231 if (!spawnparticles)
1233 count *= 300 * cl_particles_quality.value;
1235 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);
1236 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1238 else if (effectnameindex == EFFECT_EF_STARDUST)
1240 if (!spawnparticles)
1242 count *= 200 * cl_particles_quality.value;
1244 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);
1245 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1247 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1251 int smoke, blood, bubbles, r, color, count;
1253 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1256 Vector4Set(light, 0, 0, 0, 0);
1258 if (effectnameindex == EFFECT_TR_ROCKET)
1259 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1260 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1262 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1263 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1265 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1267 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1268 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1272 matrix4x4_t tempmatrix;
1273 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1274 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1275 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1279 if (!spawnparticles)
1282 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1285 VectorSubtract(originmaxs, originmins, dir);
1286 len = VectorNormalizeLength(dir);
1290 dec = -ent->persistent.trail_time;
1291 ent->persistent.trail_time += len;
1292 if (ent->persistent.trail_time < 0.01f)
1295 // if we skip out, leave it reset
1296 ent->persistent.trail_time = 0.0f;
1301 // advance into this frame to reach the first puff location
1302 VectorMA(originmins, dec, dir, pos);
1305 smoke = cl_particles.integer && cl_particles_smoke.integer;
1306 blood = cl_particles.integer && cl_particles_blood.integer;
1307 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1308 qd = 1.0f / cl_particles_quality.value;
1311 while (len >= 0 && ++count <= 16384)
1316 if (effectnameindex == EFFECT_TR_BLOOD)
1318 if (cl_particles_quake.integer)
1320 color = particlepalette[67 + (rand()&3)];
1321 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);
1326 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);
1329 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1331 if (cl_particles_quake.integer)
1334 color = particlepalette[67 + (rand()&3)];
1335 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);
1340 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);
1346 if (effectnameindex == EFFECT_TR_ROCKET)
1348 if (cl_particles_quake.integer)
1351 color = particlepalette[ramp3[r]];
1352 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);
1356 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);
1357 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);
1360 else if (effectnameindex == EFFECT_TR_GRENADE)
1362 if (cl_particles_quake.integer)
1365 color = particlepalette[ramp3[r]];
1366 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);
1370 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);
1373 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1375 if (cl_particles_quake.integer)
1378 color = particlepalette[52 + (rand()&7)];
1379 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);
1380 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);
1382 else if (gamemode == GAME_GOODVSBAD2)
1385 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);
1389 color = particlepalette[20 + (rand()&7)];
1390 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);
1393 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1395 if (cl_particles_quake.integer)
1398 color = particlepalette[230 + (rand()&7)];
1399 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);
1400 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);
1404 color = particlepalette[226 + (rand()&7)];
1405 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);
1408 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1410 if (cl_particles_quake.integer)
1412 color = particlepalette[152 + (rand()&3)];
1413 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);
1415 else if (gamemode == GAME_GOODVSBAD2)
1418 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);
1420 else if (gamemode == GAME_PRYDON)
1423 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);
1426 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);
1428 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1431 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);
1433 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1436 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);
1438 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1439 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);
1443 if (effectnameindex == EFFECT_TR_ROCKET)
1444 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);
1445 else if (effectnameindex == EFFECT_TR_GRENADE)
1446 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);
1448 // advance to next time and position
1451 VectorMA (pos, dec, dir, pos);
1454 ent->persistent.trail_time = len;
1457 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1460 // this is also called on point effects with spawndlight = true and
1461 // spawnparticles = true
1462 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)
1464 qboolean found = false;
1466 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1468 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1469 return; // no such effect
1471 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1473 int effectinfoindex;
1476 particleeffectinfo_t *info;
1488 qboolean underwater;
1489 qboolean immediatebloodstain;
1491 float avgtint[4], tint[4], tintlerp;
1492 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1493 VectorLerp(originmins, 0.5, originmaxs, center);
1494 supercontents = CL_PointSuperContents(center);
1495 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1496 VectorSubtract(originmaxs, originmins, traildir);
1497 traillen = VectorLength(traildir);
1498 VectorNormalize(traildir);
1501 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1505 Vector4Set(avgtint, 1, 1, 1, 1);
1507 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1509 if ((info->effectnameindex == effectnameindex) && (info->flags & PARTICLEEFFECT_DEFINED))
1511 qboolean definedastrail = info->trailspacing > 0;
1513 qboolean drawastrail = wanttrail;
1514 if (cl_particles_forcetraileffects.integer)
1515 drawastrail = drawastrail || definedastrail;
1518 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1520 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1523 // spawn a dlight if requested
1524 if (info->lightradiusstart > 0 && spawndlight)
1526 matrix4x4_t tempmatrix;
1528 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1530 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1531 if (info->lighttime > 0 && info->lightradiusfade > 0)
1533 // light flash (explosion, etc)
1534 // called when effect starts
1535 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);
1537 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1540 // called by CL_LinkNetworkEntity
1541 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1542 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1543 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1544 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1545 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);
1546 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1550 if (!spawnparticles)
1555 if (info->tex[1] > info->tex[0])
1557 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1558 tex = min(tex, info->tex[1] - 1);
1560 if(info->staintex[0] < 0)
1561 staintex = info->staintex[0];
1564 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1565 staintex = min(staintex, info->staintex[1] - 1);
1567 if (info->particletype == pt_decal)
1569 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1570 AnglesFromVectors(angles, velocity, NULL, false);
1571 AngleVectors(angles, forward, right, up);
1572 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1574 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]);
1576 else if (info->orientation == PARTICLE_HBEAM)
1581 AnglesFromVectors(angles, traildir, NULL, false);
1582 AngleVectors(angles, forward, right, up);
1583 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1585 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);
1590 if (!cl_particles.integer)
1592 switch (info->particletype)
1594 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1595 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1596 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1597 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1598 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1599 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1603 cnt = info->countabsolute;
1604 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1605 // if drawastrail is not set, we will
1606 // use the regular cnt-based random
1607 // particle spawning at the center; so
1608 // do NOT apply trailspacing then!
1609 if (drawastrail && definedastrail)
1610 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1613 continue; // nothing to draw
1614 info->particleaccumulator += cnt;
1616 if (drawastrail || definedastrail)
1617 immediatebloodstain = false;
1619 immediatebloodstain =
1620 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1622 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1626 VectorCopy(originmins, trailpos);
1627 trailstep = traillen / cnt;
1631 VectorCopy(center, trailpos);
1637 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1638 AnglesFromVectors(angles, velocity, NULL, false);
1641 AnglesFromVectors(angles, traildir, NULL, false);
1643 AngleVectors(angles, forward, right, up);
1644 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1645 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1646 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1647 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1649 if (info->tex[1] > info->tex[0])
1651 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1652 tex = min(tex, info->tex[1] - 1);
1654 if (!(drawastrail || definedastrail))
1656 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1657 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1658 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1662 tintlerp = lhrandom(0, 1);
1663 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1666 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);
1667 if (immediatebloodstain && part)
1669 immediatebloodstain = false;
1670 CL_ImmediateBloodStain(part);
1673 VectorMA(trailpos, trailstep, traildir, trailpos);
1680 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, wanttrail);
1683 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)
1685 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1688 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)
1690 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1693 // note: this one ONLY does boxes!
1694 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)
1696 CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1704 void CL_EntityParticles (const entity_t *ent)
1707 vec_t pitch, yaw, dist = 64, beamlength = 16;
1709 static vec3_t avelocities[NUMVERTEXNORMALS];
1710 if (!cl_particles.integer) return;
1711 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1713 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1715 if (!avelocities[0][0])
1716 for (i = 0;i < NUMVERTEXNORMALS;i++)
1717 for (j = 0;j < 3;j++)
1718 avelocities[i][j] = lhrandom(0, 2.55);
1720 for (i = 0;i < NUMVERTEXNORMALS;i++)
1722 yaw = cl.time * avelocities[i][0];
1723 pitch = cl.time * avelocities[i][1];
1724 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1725 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1726 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1727 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);
1732 void CL_ReadPointFile_f (void)
1734 double org[3], leakorg[3];
1737 char *pointfile = NULL, *pointfilepos, *t, tchar;
1738 char name[MAX_QPATH];
1743 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1744 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1747 Con_Printf("Could not open %s\n", name);
1751 Con_Printf("Reading %s...\n", name);
1752 VectorClear(leakorg);
1755 pointfilepos = pointfile;
1756 while (*pointfilepos)
1758 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1763 while (*t && *t != '\n' && *t != '\r')
1767 #if _MSC_VER >= 1400
1768 #define sscanf sscanf_s
1770 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1771 VectorCopy(org, vecorg);
1777 VectorCopy(org, leakorg);
1780 if (cl.num_particles < cl.max_particles - 3)
1783 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);
1786 Mem_Free(pointfile);
1787 VectorCopy(leakorg, vecorg);
1788 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1795 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);
1796 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);
1797 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);
1802 CL_ParseParticleEffect
1804 Parse an effect out of the server message
1807 void CL_ParseParticleEffect (void)
1810 int i, count, msgcount, color;
1812 MSG_ReadVector(&cl_message, org, cls.protocol);
1813 for (i=0 ; i<3 ; i++)
1814 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1815 msgcount = MSG_ReadByte(&cl_message);
1816 color = MSG_ReadByte(&cl_message);
1818 if (msgcount == 255)
1823 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1828 CL_ParticleExplosion
1832 void CL_ParticleExplosion (const vec3_t org)
1838 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1839 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1841 if (cl_particles_quake.integer)
1843 for (i = 0;i < 1024;i++)
1849 color = particlepalette[ramp1[r]];
1850 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);
1854 color = particlepalette[ramp2[r]];
1855 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);
1861 i = CL_PointSuperContents(org);
1862 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1864 if (cl_particles.integer && cl_particles_bubbles.integer)
1865 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1866 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);
1870 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1872 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1879 VectorMA(org, 128, v2, v);
1880 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, collision_extendmovelength.value, true, false, NULL, false, false);
1882 while (k < 16 && trace.fraction < 0.1f);
1883 VectorSubtract(trace.endpos, org, v2);
1884 VectorScale(v2, 2.0f, v2);
1885 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);
1891 if (cl_particles_explosions_shell.integer)
1892 R_NewExplosion(org);
1897 CL_ParticleExplosion2
1901 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1904 if (!cl_particles.integer) return;
1906 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1908 k = particlepalette[colorStart + (i % colorLength)];
1909 if (cl_particles_quake.integer)
1910 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);
1912 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);
1916 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1919 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1920 if (cl_particles_sparks.integer)
1922 sparkcount *= cl_particles_quality.value;
1923 while(sparkcount-- > 0)
1924 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);
1928 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1931 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1932 if (cl_particles_smoke.integer)
1934 smokecount *= cl_particles_quality.value;
1935 while(smokecount-- > 0)
1936 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);
1940 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)
1944 if (!cl_particles.integer) return;
1945 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1947 count = (int)(count * cl_particles_quality.value);
1950 k = particlepalette[colorbase + (rand()&3)];
1951 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);
1955 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1958 float minz, maxz, lifetime = 30;
1960 if (!cl_particles.integer) return;
1961 if (dir[2] < 0) // falling
1963 minz = maxs[2] + dir[2] * 0.1;
1966 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1971 maxz = maxs[2] + dir[2] * 0.1;
1973 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1976 count = (int)(count * cl_particles_quality.value);
1981 if (!cl_particles_rain.integer) break;
1982 count *= 4; // ick, this should be in the mod or maps?
1986 k = particlepalette[colorbase + (rand()&3)];
1987 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1988 if (gamemode == GAME_GOODVSBAD2)
1989 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);
1991 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);
1995 if (!cl_particles_snow.integer) break;
1998 k = particlepalette[colorbase + (rand()&3)];
1999 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
2000 if (gamemode == GAME_GOODVSBAD2)
2001 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);
2003 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);
2007 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
2011 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
2012 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
2013 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
2014 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
2015 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
2016 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
2018 #define PARTICLETEXTURESIZE 64
2019 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
2021 static unsigned char shadebubble(float dx, float dy, vec3_t light)
2025 dz = 1 - (dx*dx+dy*dy);
2026 if (dz > 0) // it does hit the sphere
2030 normal[0] = dx;normal[1] = dy;normal[2] = dz;
2031 VectorNormalize(normal);
2032 dot = DotProduct(normal, light);
2033 if (dot > 0.5) // interior reflection
2034 f += ((dot * 2) - 1);
2035 else if (dot < -0.5) // exterior reflection
2036 f += ((dot * -2) - 1);
2038 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
2039 VectorNormalize(normal);
2040 dot = DotProduct(normal, light);
2041 if (dot > 0.5) // interior reflection
2042 f += ((dot * 2) - 1);
2043 else if (dot < -0.5) // exterior reflection
2044 f += ((dot * -2) - 1);
2046 f += 16; // just to give it a haze so you can see the outline
2047 f = bound(0, f, 255);
2048 return (unsigned char) f;
2054 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
2055 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
2057 *basex = (texnum % particlefontcols) * particlefontcellwidth;
2058 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
2059 *width = particlefontcellwidth;
2060 *height = particlefontcellheight;
2063 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
2065 int basex, basey, w, h, y;
2066 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2067 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2068 Sys_Error("invalid particle texture size for autogenerating");
2069 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2070 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2073 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2076 float cx, cy, dx, dy, f, iradius;
2078 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2079 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2080 iradius = 1.0f / radius;
2081 alpha *= (1.0f / 255.0f);
2082 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2084 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2088 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2093 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2094 d[0] += (int)(f * (blue - d[0]));
2095 d[1] += (int)(f * (green - d[1]));
2096 d[2] += (int)(f * (red - d[2]));
2103 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2106 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2108 data[0] = bound(minb, data[0], maxb);
2109 data[1] = bound(ming, data[1], maxg);
2110 data[2] = bound(minr, data[2], maxr);
2115 static void particletextureinvert(unsigned char *data)
2118 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2120 data[0] = 255 - data[0];
2121 data[1] = 255 - data[1];
2122 data[2] = 255 - data[2];
2126 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2127 static void R_InitBloodTextures (unsigned char *particletexturedata)
2130 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2131 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2134 for (i = 0;i < 8;i++)
2136 memset(data, 255, datasize);
2137 for (k = 0;k < 24;k++)
2138 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2139 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2140 particletextureinvert(data);
2141 setuptex(tex_bloodparticle[i], data, particletexturedata);
2145 for (i = 0;i < 8;i++)
2147 memset(data, 255, datasize);
2149 for (j = 1;j < 10;j++)
2150 for (k = min(j, m - 1);k < m;k++)
2151 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2152 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2153 particletextureinvert(data);
2154 setuptex(tex_blooddecal[i], data, particletexturedata);
2160 //uncomment this to make engine save out particle font to a tga file when run
2161 //#define DUMPPARTICLEFONT
2163 static void R_InitParticleTexture (void)
2165 int x, y, d, i, k, m;
2166 int basex, basey, w, h;
2167 float dx, dy, f, s1, t1, s2, t2;
2170 fs_offset_t filesize;
2171 char texturename[MAX_QPATH];
2174 // a note: decals need to modulate (multiply) the background color to
2175 // properly darken it (stain), and they need to be able to alpha fade,
2176 // this is a very difficult challenge because it means fading to white
2177 // (no change to background) rather than black (darkening everything
2178 // behind the whole decal polygon), and to accomplish this the texture is
2179 // inverted (dark red blood on white background becomes brilliant cyan
2180 // and white on black background) so we can alpha fade it to black, then
2181 // we invert it again during the blendfunc to make it work...
2183 #ifndef DUMPPARTICLEFONT
2184 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2187 particlefonttexture = decalskinframe->base;
2188 // TODO maybe allow custom grid size?
2189 particlefontwidth = image_width;
2190 particlefontheight = image_height;
2191 particlefontcellwidth = image_width / 8;
2192 particlefontcellheight = image_height / 8;
2193 particlefontcols = 8;
2194 particlefontrows = 8;
2199 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2200 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2201 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2202 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2203 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2205 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2206 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2207 particlefontcols = 8;
2208 particlefontrows = 8;
2210 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2213 for (i = 0;i < 8;i++)
2215 memset(data, 255, datasize);
2218 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2219 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2221 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2223 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2224 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2226 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2227 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2229 d = (int)(d * (1-(dx*dx+dy*dy)));
2230 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2231 d = bound(0, d, 255);
2232 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2239 setuptex(tex_smoke[i], data, particletexturedata);
2243 memset(data, 255, datasize);
2244 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2246 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2247 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2249 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2250 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2251 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2254 setuptex(tex_rainsplash, data, particletexturedata);
2257 memset(data, 255, datasize);
2258 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2260 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2261 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2263 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2264 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2265 d = bound(0, d, 255);
2266 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2269 setuptex(tex_particle, data, particletexturedata);
2272 memset(data, 255, datasize);
2273 light[0] = 1;light[1] = 1;light[2] = 1;
2274 VectorNormalize(light);
2275 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2277 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2278 // stretch upper half of bubble by +50% and shrink lower half by -50%
2279 // (this gives an elongated teardrop shape)
2281 dy = (dy - 0.5f) * 2.0f;
2283 dy = (dy - 0.5f) / 1.5f;
2284 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2286 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2287 // shrink bubble width to half
2289 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2292 setuptex(tex_raindrop, data, particletexturedata);
2295 memset(data, 255, datasize);
2296 light[0] = 1;light[1] = 1;light[2] = 1;
2297 VectorNormalize(light);
2298 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2300 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2301 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2303 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2304 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2307 setuptex(tex_bubble, data, particletexturedata);
2309 // Blood particles and blood decals
2310 R_InitBloodTextures (particletexturedata);
2313 for (i = 0;i < 8;i++)
2315 memset(data, 255, datasize);
2316 for (k = 0;k < 12;k++)
2317 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2318 for (k = 0;k < 3;k++)
2319 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2320 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2321 particletextureinvert(data);
2322 setuptex(tex_bulletdecal[i], data, particletexturedata);
2325 #ifdef DUMPPARTICLEFONT
2326 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2329 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2330 particlefonttexture = decalskinframe->base;
2332 Mem_Free(particletexturedata);
2337 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2339 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2340 particletexture[i].texture = particlefonttexture;
2341 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2342 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2343 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2344 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2347 #ifndef DUMPPARTICLEFONT
2348 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2349 if (!particletexture[tex_beam].texture)
2352 unsigned char noise3[64][64], data2[64][16][4];
2354 fractalnoise(&noise3[0][0], 64, 4);
2356 for (y = 0;y < 64;y++)
2358 dy = (y - 0.5f*64) / (64*0.5f-1);
2359 for (x = 0;x < 16;x++)
2361 dx = (x - 0.5f*16) / (16*0.5f-2);
2362 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2363 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2364 data2[y][x][3] = 255;
2368 #ifdef DUMPPARTICLEFONT
2369 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2371 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2373 particletexture[tex_beam].s1 = 0;
2374 particletexture[tex_beam].t1 = 0;
2375 particletexture[tex_beam].s2 = 1;
2376 particletexture[tex_beam].t2 = 1;
2378 // now load an texcoord/texture override file
2379 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2386 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2388 if(!strcmp(com_token, "\n"))
2389 continue; // empty line
2390 i = atoi(com_token);
2398 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2400 strlcpy(texturename, com_token, sizeof(texturename));
2401 s1 = atof(com_token);
2402 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2405 t1 = atof(com_token);
2406 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2408 s2 = atof(com_token);
2409 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2411 t2 = atof(com_token);
2412 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2413 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2414 strlcpy(texturename, com_token, sizeof(texturename));
2421 if (!texturename[0])
2423 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2426 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2428 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2431 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2434 // R_SkinFrame_LoadExternal already complained
2437 particletexture[i].texture = sf->base;
2438 particletexture[i].s1 = s1;
2439 particletexture[i].t1 = t1;
2440 particletexture[i].s2 = s2;
2441 particletexture[i].t2 = t2;
2447 static void r_part_start(void)
2450 // generate particlepalette for convenience from the main one
2451 for (i = 0;i < 256;i++)
2452 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2453 particletexturepool = R_AllocTexturePool();
2454 R_InitParticleTexture ();
2455 CL_Particles_LoadEffectInfo(NULL);
2458 static void r_part_shutdown(void)
2460 R_FreeTexturePool(&particletexturepool);
2463 static void r_part_newmap(void)
2466 R_SkinFrame_MarkUsed(decalskinframe);
2467 CL_Particles_LoadEffectInfo(NULL);
2470 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2471 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2473 void R_Particles_Init (void)
2476 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2478 particle_elements[i*6+0] = i*4+0;
2479 particle_elements[i*6+1] = i*4+1;
2480 particle_elements[i*6+2] = i*4+2;
2481 particle_elements[i*6+3] = i*4+0;
2482 particle_elements[i*6+4] = i*4+2;
2483 particle_elements[i*6+5] = i*4+3;
2486 Cvar_RegisterVariable(&r_drawparticles);
2487 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2488 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2489 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2490 Cvar_RegisterVariable(&r_drawdecals);
2491 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2492 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2495 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2497 int surfacelistindex;
2499 float *v3f, *t2f, *c4f;
2500 particletexture_t *tex;
2501 vec_t right[3], up[3], size, ca;
2502 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2504 RSurf_ActiveWorldEntity();
2506 r_refdef.stats[r_stat_drawndecals] += numsurfaces;
2507 // R_Mesh_ResetTextureState();
2508 GL_DepthMask(false);
2509 GL_DepthRange(0, 1);
2510 GL_PolygonOffset(0, 0);
2512 GL_CullFace(GL_NONE);
2514 // generate all the vertices at once
2515 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2517 d = cl.decals + surfacelist[surfacelistindex];
2520 c4f = particle_color4f + 16*surfacelistindex;
2521 ca = d->alpha * alphascale;
2522 // ensure alpha multiplier saturates properly
2523 if (ca > 1.0f / 256.0f)
2525 if (r_refdef.fogenabled)
2526 ca *= RSurf_FogVertex(d->org);
2527 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2528 Vector4Copy(c4f, c4f + 4);
2529 Vector4Copy(c4f, c4f + 8);
2530 Vector4Copy(c4f, c4f + 12);
2532 // calculate vertex positions
2533 size = d->size * cl_particles_size.value;
2534 VectorVectors(d->normal, right, up);
2535 VectorScale(right, size, right);
2536 VectorScale(up, size, up);
2537 v3f = particle_vertex3f + 12*surfacelistindex;
2538 v3f[ 0] = d->org[0] - right[0] - up[0];
2539 v3f[ 1] = d->org[1] - right[1] - up[1];
2540 v3f[ 2] = d->org[2] - right[2] - up[2];
2541 v3f[ 3] = d->org[0] - right[0] + up[0];
2542 v3f[ 4] = d->org[1] - right[1] + up[1];
2543 v3f[ 5] = d->org[2] - right[2] + up[2];
2544 v3f[ 6] = d->org[0] + right[0] + up[0];
2545 v3f[ 7] = d->org[1] + right[1] + up[1];
2546 v3f[ 8] = d->org[2] + right[2] + up[2];
2547 v3f[ 9] = d->org[0] + right[0] - up[0];
2548 v3f[10] = d->org[1] + right[1] - up[1];
2549 v3f[11] = d->org[2] + right[2] - up[2];
2551 // calculate texcoords
2552 tex = &particletexture[d->texnum];
2553 t2f = particle_texcoord2f + 8*surfacelistindex;
2554 t2f[0] = tex->s1;t2f[1] = tex->t2;
2555 t2f[2] = tex->s1;t2f[3] = tex->t1;
2556 t2f[4] = tex->s2;t2f[5] = tex->t1;
2557 t2f[6] = tex->s2;t2f[7] = tex->t2;
2560 // now render the decals all at once
2561 // (this assumes they all use one particle font texture!)
2562 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2563 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2564 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2565 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2568 void R_DrawDecals (void)
2571 int drawdecals = r_drawdecals.integer;
2576 unsigned int killsequence = cl.decalsequence - bound(0, (unsigned int) cl_decals_max.integer, cl.decalsequence);
2578 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2579 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2581 // LordHavoc: early out conditions
2585 decalfade = frametime * 256 / cl_decals_fadetime.value;
2586 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2587 drawdist2 = drawdist2*drawdist2;
2589 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2591 if (!decal->typeindex)
2594 if (killsequence > decal->decalsequence)
2597 if (cl.time > decal->time2 + cl_decals_time.value)
2599 decal->alpha -= decalfade;
2600 if (decal->alpha <= 0)
2606 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2608 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2609 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2615 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2621 if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size))
2622 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2625 decal->typeindex = 0;
2626 if (cl.free_decal > i)
2630 // reduce cl.num_decals if possible
2631 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2634 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2636 decal_t *olddecals = cl.decals;
2637 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2638 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2639 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2640 Mem_Free(olddecals);
2643 r_refdef.stats[r_stat_totaldecals] = cl.num_decals;
2646 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2648 vec3_t vecorg, vecvel, baseright, baseup;
2649 int surfacelistindex;
2650 int batchstart, batchcount;
2651 const particle_t *p;
2653 rtexture_t *texture;
2654 float *v3f, *t2f, *c4f;
2655 particletexture_t *tex;
2656 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2657 // float ambient[3], diffuse[3], diffusenormal[3];
2658 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2659 vec4_t colormultiplier;
2660 float minparticledist_start, minparticledist_end;
2663 RSurf_ActiveWorldEntity();
2665 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));
2667 r_refdef.stats[r_stat_particles] += numsurfaces;
2668 // R_Mesh_ResetTextureState();
2669 GL_DepthMask(false);
2670 GL_DepthRange(0, 1);
2671 GL_PolygonOffset(0, 0);
2673 GL_CullFace(GL_NONE);
2675 spintime = r_refdef.scene.time;
2677 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2678 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2679 dofade = (minparticledist_start < minparticledist_end);
2681 // first generate all the vertices at once
2682 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2684 p = cl.particles + surfacelist[surfacelistindex];
2686 blendmode = (pblend_t)p->blendmode;
2688 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2689 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2690 alpha = palpha * colormultiplier[3];
2691 // ensure alpha multiplier saturates properly
2697 case PBLEND_INVALID:
2699 // additive and modulate can just fade out in fog (this is correct)
2700 if (r_refdef.fogenabled)
2701 alpha *= RSurf_FogVertex(p->org);
2702 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2703 alpha *= 1.0f / 256.0f;
2704 c4f[0] = p->color[0] * alpha;
2705 c4f[1] = p->color[1] * alpha;
2706 c4f[2] = p->color[2] * alpha;
2710 // additive and modulate can just fade out in fog (this is correct)
2711 if (r_refdef.fogenabled)
2712 alpha *= RSurf_FogVertex(p->org);
2713 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2714 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2715 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2716 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2720 c4f[0] = p->color[0] * colormultiplier[0];
2721 c4f[1] = p->color[1] * colormultiplier[1];
2722 c4f[2] = p->color[2] * colormultiplier[2];
2724 // note: lighting is not cheap!
2725 if (particletype[p->typeindex].lighting)
2727 vecorg[0] = p->org[0];
2728 vecorg[1] = p->org[1];
2729 vecorg[2] = p->org[2];
2730 R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2732 // mix in the fog color
2733 if (r_refdef.fogenabled)
2735 fog = RSurf_FogVertex(p->org);
2737 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2738 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2739 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2741 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2742 VectorScale(c4f, alpha, c4f);
2745 // copy the color into the other three vertices
2746 Vector4Copy(c4f, c4f + 4);
2747 Vector4Copy(c4f, c4f + 8);
2748 Vector4Copy(c4f, c4f + 12);
2750 size = p->size * cl_particles_size.value;
2751 tex = &particletexture[p->texnum];
2752 switch(p->orientation)
2754 // case PARTICLE_INVALID:
2755 case PARTICLE_BILLBOARD:
2756 if (p->angle + p->spin)
2758 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2759 spinsin = sin(spinrad) * size;
2760 spincos = cos(spinrad) * size;
2761 spinm1 = -p->stretch * spincos;
2764 spinm4 = -p->stretch * spincos;
2765 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2766 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2770 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2771 VectorScale(r_refdef.view.up, size, up);
2774 v3f[ 0] = p->org[0] - right[0] - up[0];
2775 v3f[ 1] = p->org[1] - right[1] - up[1];
2776 v3f[ 2] = p->org[2] - right[2] - up[2];
2777 v3f[ 3] = p->org[0] - right[0] + up[0];
2778 v3f[ 4] = p->org[1] - right[1] + up[1];
2779 v3f[ 5] = p->org[2] - right[2] + up[2];
2780 v3f[ 6] = p->org[0] + right[0] + up[0];
2781 v3f[ 7] = p->org[1] + right[1] + up[1];
2782 v3f[ 8] = p->org[2] + right[2] + up[2];
2783 v3f[ 9] = p->org[0] + right[0] - up[0];
2784 v3f[10] = p->org[1] + right[1] - up[1];
2785 v3f[11] = p->org[2] + right[2] - up[2];
2786 t2f[0] = tex->s1;t2f[1] = tex->t2;
2787 t2f[2] = tex->s1;t2f[3] = tex->t1;
2788 t2f[4] = tex->s2;t2f[5] = tex->t1;
2789 t2f[6] = tex->s2;t2f[7] = tex->t2;
2791 case PARTICLE_ORIENTED_DOUBLESIDED:
2792 vecvel[0] = p->vel[0];
2793 vecvel[1] = p->vel[1];
2794 vecvel[2] = p->vel[2];
2795 VectorVectors(vecvel, baseright, baseup);
2796 if (p->angle + p->spin)
2798 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2799 spinsin = sin(spinrad) * size;
2800 spincos = cos(spinrad) * size;
2801 spinm1 = p->stretch * spincos;
2804 spinm4 = p->stretch * spincos;
2805 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2806 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2810 VectorScale(baseright, size * p->stretch, right);
2811 VectorScale(baseup, size, up);
2813 v3f[ 0] = p->org[0] - right[0] - up[0];
2814 v3f[ 1] = p->org[1] - right[1] - up[1];
2815 v3f[ 2] = p->org[2] - right[2] - up[2];
2816 v3f[ 3] = p->org[0] - right[0] + up[0];
2817 v3f[ 4] = p->org[1] - right[1] + up[1];
2818 v3f[ 5] = p->org[2] - right[2] + up[2];
2819 v3f[ 6] = p->org[0] + right[0] + up[0];
2820 v3f[ 7] = p->org[1] + right[1] + up[1];
2821 v3f[ 8] = p->org[2] + right[2] + up[2];
2822 v3f[ 9] = p->org[0] + right[0] - up[0];
2823 v3f[10] = p->org[1] + right[1] - up[1];
2824 v3f[11] = p->org[2] + right[2] - up[2];
2825 t2f[0] = tex->s1;t2f[1] = tex->t2;
2826 t2f[2] = tex->s1;t2f[3] = tex->t1;
2827 t2f[4] = tex->s2;t2f[5] = tex->t1;
2828 t2f[6] = tex->s2;t2f[7] = tex->t2;
2830 case PARTICLE_SPARK:
2831 len = VectorLength(p->vel);
2832 VectorNormalize2(p->vel, up);
2833 lenfactor = p->stretch * 0.04 * len;
2834 if(lenfactor < size * 0.5)
2835 lenfactor = size * 0.5;
2836 VectorMA(p->org, -lenfactor, up, v);
2837 VectorMA(p->org, lenfactor, up, up2);
2838 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2839 t2f[0] = tex->s1;t2f[1] = tex->t2;
2840 t2f[2] = tex->s1;t2f[3] = tex->t1;
2841 t2f[4] = tex->s2;t2f[5] = tex->t1;
2842 t2f[6] = tex->s2;t2f[7] = tex->t2;
2844 case PARTICLE_VBEAM:
2845 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2846 VectorSubtract(p->vel, p->org, up);
2847 VectorNormalize(up);
2848 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2849 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2850 t2f[0] = tex->s2;t2f[1] = v[0];
2851 t2f[2] = tex->s1;t2f[3] = v[0];
2852 t2f[4] = tex->s1;t2f[5] = v[1];
2853 t2f[6] = tex->s2;t2f[7] = v[1];
2855 case PARTICLE_HBEAM:
2856 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2857 VectorSubtract(p->vel, p->org, up);
2858 VectorNormalize(up);
2859 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2860 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2861 t2f[0] = v[0];t2f[1] = tex->t1;
2862 t2f[2] = v[0];t2f[3] = tex->t2;
2863 t2f[4] = v[1];t2f[5] = tex->t2;
2864 t2f[6] = v[1];t2f[7] = tex->t1;
2869 // now render batches of particles based on blendmode and texture
2870 blendmode = PBLEND_INVALID;
2874 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2875 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2877 p = cl.particles + surfacelist[surfacelistindex];
2879 if (texture != particletexture[p->texnum].texture)
2881 texture = particletexture[p->texnum].texture;
2882 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2885 if (p->blendmode == PBLEND_INVMOD)
2887 // inverse modulate blend - group these
2888 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2889 // iterate until we find a change in settings
2890 batchstart = surfacelistindex++;
2891 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2893 p = cl.particles + surfacelist[surfacelistindex];
2894 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2900 // additive or alpha blend - group these
2901 // (we can group these because we premultiplied the texture alpha)
2902 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2903 // iterate until we find a change in settings
2904 batchstart = surfacelistindex++;
2905 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2907 p = cl.particles + surfacelist[surfacelistindex];
2908 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2913 batchcount = surfacelistindex - batchstart;
2914 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2918 void R_DrawParticles (void)
2921 int drawparticles = r_drawparticles.integer;
2922 float minparticledist_start;
2924 float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2930 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2931 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2933 // LordHavoc: early out conditions
2934 if (!cl.num_particles)
2937 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2938 gravity = frametime * cl.movevars_gravity;
2939 update = frametime > 0;
2940 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2941 drawdist2 = drawdist2*drawdist2;
2943 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2947 if (cl.free_particle > i)
2948 cl.free_particle = i;
2954 if (p->delayedspawn > cl.time)
2957 p->size += p->sizeincrease * frametime;
2958 p->alpha -= p->alphafade * frametime;
2960 if (p->alpha <= 0 || p->die <= cl.time)
2963 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2965 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2967 if (p->typeindex == pt_blood)
2968 p->size += frametime * 8;
2970 p->vel[2] -= p->gravity * gravity;
2971 f = 1.0f - min(p->liquidfriction * frametime, 1);
2972 VectorScale(p->vel, f, p->vel);
2976 p->vel[2] -= p->gravity * gravity;
2979 f = 1.0f - min(p->airfriction * frametime, 1);
2980 VectorScale(p->vel, f, p->vel);
2984 VectorCopy(p->org, oldorg);
2985 VectorMA(p->org, frametime, p->vel, p->org);
2986 // if (p->bounce && cl.time >= p->delayedcollisions)
2987 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2989 trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), collision_extendmovelength.value, true, false, &hitent, false, false);
2990 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2991 // or if the trace hit something flagged as NOIMPACT
2992 // then remove the particle
2993 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2995 VectorCopy(trace.endpos, p->org);
2996 // react if the particle hit something
2997 if (trace.fraction < 1)
2999 VectorCopy(trace.endpos, p->org);
3001 if (p->staintexnum >= 0)
3003 // blood - splash on solid
3004 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
3007 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
3008 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
3009 if (cl_decals.integer)
3011 // create a decal for the blood splat
3012 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
3013 if (cl_decals_newsystem_bloodsmears.integer)
3015 VectorCopy(p->vel, decaldir);
3016 VectorNormalize(decaldir);
3019 VectorCopy(trace.plane.normal, decaldir);
3020 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
3025 if (p->typeindex == pt_blood)
3027 // blood - splash on solid
3028 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
3030 if(p->staintexnum == -1) // staintex < -1 means no stains at all
3032 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)));
3033 if (cl_decals.integer)
3035 // create a decal for the blood splat
3036 if (cl_decals_newsystem_bloodsmears.integer)
3038 VectorCopy(p->vel, decaldir);
3039 VectorNormalize(decaldir);
3042 VectorCopy(trace.plane.normal, decaldir);
3043 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);
3048 else if (p->bounce < 0)
3050 // bounce -1 means remove on impact
3055 // anything else - bounce off solid
3056 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
3057 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
3062 if (VectorLength2(p->vel) < 0.03)
3064 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
3066 VectorClear(p->vel);
3070 if (p->typeindex != pt_static)
3072 switch (p->typeindex)
3074 case pt_entityparticle:
3075 // particle that removes itself after one rendered frame
3082 a = CL_PointSuperContents(p->org);
3083 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
3087 a = CL_PointSuperContents(p->org);
3088 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
3092 a = CL_PointSuperContents(p->org);
3093 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3097 if (cl.time > p->time2)
3100 p->time2 = cl.time + (rand() & 3) * 0.1;
3101 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3102 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3104 a = CL_PointSuperContents(p->org);
3105 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3113 else if (p->delayedspawn > cl.time)
3117 // don't render particles too close to the view (they chew fillrate)
3118 // also don't render particles behind the view (useless)
3119 // further checks to cull to the frustum would be too slow here
3120 switch(p->typeindex)
3123 // beams have no culling
3124 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3127 if(cl_particles_visculling.integer)
3128 if (!r_refdef.viewcache.world_novis)
3129 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3131 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3133 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3136 // anything else just has to be in front of the viewer and visible at this distance
3137 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3138 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3145 if (cl.free_particle > i)
3146 cl.free_particle = i;
3149 // reduce cl.num_particles if possible
3150 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3153 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3155 particle_t *oldparticles = cl.particles;
3156 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3157 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3158 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3159 Mem_Free(oldparticles);