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;
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);
1289 dec = -ent->persistent.trail_time;
1290 ent->persistent.trail_time += len;
1291 if (ent->persistent.trail_time < 0.01f)
1294 // if we skip out, leave it reset
1295 ent->persistent.trail_time = 0.0f;
1300 // advance into this frame to reach the first puff location
1301 VectorMA(originmins, dec, dir, pos);
1304 smoke = cl_particles.integer && cl_particles_smoke.integer;
1305 blood = cl_particles.integer && cl_particles_blood.integer;
1306 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1307 qd = 1.0f / cl_particles_quality.value;
1314 if (effectnameindex == EFFECT_TR_BLOOD)
1316 if (cl_particles_quake.integer)
1318 color = particlepalette[67 + (rand()&3)];
1319 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);
1324 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);
1327 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1329 if (cl_particles_quake.integer)
1332 color = particlepalette[67 + (rand()&3)];
1333 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);
1338 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);
1344 if (effectnameindex == EFFECT_TR_ROCKET)
1346 if (cl_particles_quake.integer)
1349 color = particlepalette[ramp3[r]];
1350 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);
1354 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);
1355 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);
1358 else if (effectnameindex == EFFECT_TR_GRENADE)
1360 if (cl_particles_quake.integer)
1363 color = particlepalette[ramp3[r]];
1364 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);
1368 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);
1371 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1373 if (cl_particles_quake.integer)
1376 color = particlepalette[52 + (rand()&7)];
1377 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);
1378 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 else if (gamemode == GAME_GOODVSBAD2)
1383 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);
1387 color = particlepalette[20 + (rand()&7)];
1388 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);
1391 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1393 if (cl_particles_quake.integer)
1396 color = particlepalette[230 + (rand()&7)];
1397 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);
1398 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);
1402 color = particlepalette[226 + (rand()&7)];
1403 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);
1406 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1408 if (cl_particles_quake.integer)
1410 color = particlepalette[152 + (rand()&3)];
1411 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);
1413 else if (gamemode == GAME_GOODVSBAD2)
1416 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);
1418 else if (gamemode == GAME_PRYDON)
1421 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);
1424 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);
1426 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1429 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);
1431 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1434 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);
1436 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1437 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);
1441 if (effectnameindex == EFFECT_TR_ROCKET)
1442 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);
1443 else if (effectnameindex == EFFECT_TR_GRENADE)
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);
1446 // advance to next time and position
1449 VectorMA (pos, dec, dir, pos);
1452 ent->persistent.trail_time = len;
1455 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1458 // this is also called on point effects with spawndlight = true and
1459 // spawnparticles = true
1460 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)
1462 qboolean found = false;
1464 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1466 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1467 return; // no such effect
1469 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1471 int effectinfoindex;
1474 particleeffectinfo_t *info;
1486 qboolean underwater;
1487 qboolean immediatebloodstain;
1489 float avgtint[4], tint[4], tintlerp;
1490 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1491 VectorLerp(originmins, 0.5, originmaxs, center);
1492 supercontents = CL_PointSuperContents(center);
1493 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1494 VectorSubtract(originmaxs, originmins, traildir);
1495 traillen = VectorLength(traildir);
1496 VectorNormalize(traildir);
1499 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1503 Vector4Set(avgtint, 1, 1, 1, 1);
1505 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1507 if ((info->effectnameindex == effectnameindex) && (info->flags & PARTICLEEFFECT_DEFINED))
1509 qboolean definedastrail = info->trailspacing > 0;
1511 qboolean drawastrail = wanttrail;
1512 if (cl_particles_forcetraileffects.integer)
1513 drawastrail = drawastrail || definedastrail;
1516 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1518 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1521 // spawn a dlight if requested
1522 if (info->lightradiusstart > 0 && spawndlight)
1524 matrix4x4_t tempmatrix;
1526 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1528 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1529 if (info->lighttime > 0 && info->lightradiusfade > 0)
1531 // light flash (explosion, etc)
1532 // called when effect starts
1533 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);
1535 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1538 // called by CL_LinkNetworkEntity
1539 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1540 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1541 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1542 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1543 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);
1544 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1548 if (!spawnparticles)
1553 if (info->tex[1] > info->tex[0])
1555 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1556 tex = min(tex, info->tex[1] - 1);
1558 if(info->staintex[0] < 0)
1559 staintex = info->staintex[0];
1562 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1563 staintex = min(staintex, info->staintex[1] - 1);
1565 if (info->particletype == pt_decal)
1567 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1568 AnglesFromVectors(angles, velocity, NULL, false);
1569 AngleVectors(angles, forward, right, up);
1570 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1572 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]);
1574 else if (info->orientation == PARTICLE_HBEAM)
1579 AnglesFromVectors(angles, traildir, NULL, false);
1580 AngleVectors(angles, forward, right, up);
1581 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1583 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);
1588 if (!cl_particles.integer)
1590 switch (info->particletype)
1592 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1593 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1594 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1595 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1596 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1597 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1601 cnt = info->countabsolute;
1602 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1603 // if drawastrail is not set, we will
1604 // use the regular cnt-based random
1605 // particle spawning at the center; so
1606 // do NOT apply trailspacing then!
1607 if (drawastrail && definedastrail)
1608 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1611 continue; // nothing to draw
1612 info->particleaccumulator += cnt;
1614 if (drawastrail || definedastrail)
1615 immediatebloodstain = false;
1617 immediatebloodstain =
1618 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1620 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1624 VectorCopy(originmins, trailpos);
1625 trailstep = traillen / cnt;
1629 VectorCopy(center, trailpos);
1635 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1636 AnglesFromVectors(angles, velocity, NULL, false);
1639 AnglesFromVectors(angles, traildir, NULL, false);
1641 AngleVectors(angles, forward, right, up);
1642 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1643 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1644 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1645 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1647 if (info->tex[1] > info->tex[0])
1649 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1650 tex = min(tex, info->tex[1] - 1);
1652 if (!(drawastrail || definedastrail))
1654 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1655 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1656 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1660 tintlerp = lhrandom(0, 1);
1661 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1664 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);
1665 if (immediatebloodstain && part)
1667 immediatebloodstain = false;
1668 CL_ImmediateBloodStain(part);
1671 VectorMA(trailpos, trailstep, traildir, trailpos);
1678 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, wanttrail);
1681 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)
1683 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1686 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)
1688 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1691 // note: this one ONLY does boxes!
1692 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)
1694 CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1702 void CL_EntityParticles (const entity_t *ent)
1705 vec_t pitch, yaw, dist = 64, beamlength = 16;
1707 static vec3_t avelocities[NUMVERTEXNORMALS];
1708 if (!cl_particles.integer) return;
1709 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1711 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1713 if (!avelocities[0][0])
1714 for (i = 0;i < NUMVERTEXNORMALS;i++)
1715 for (j = 0;j < 3;j++)
1716 avelocities[i][j] = lhrandom(0, 2.55);
1718 for (i = 0;i < NUMVERTEXNORMALS;i++)
1720 yaw = cl.time * avelocities[i][0];
1721 pitch = cl.time * avelocities[i][1];
1722 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1723 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1724 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1725 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);
1730 void CL_ReadPointFile_f (void)
1732 double org[3], leakorg[3];
1735 char *pointfile = NULL, *pointfilepos, *t, tchar;
1736 char name[MAX_QPATH];
1741 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1742 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1745 Con_Printf("Could not open %s\n", name);
1749 Con_Printf("Reading %s...\n", name);
1750 VectorClear(leakorg);
1753 pointfilepos = pointfile;
1754 while (*pointfilepos)
1756 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1761 while (*t && *t != '\n' && *t != '\r')
1765 #if _MSC_VER >= 1400
1766 #define sscanf sscanf_s
1768 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1769 VectorCopy(org, vecorg);
1775 VectorCopy(org, leakorg);
1778 if (cl.num_particles < cl.max_particles - 3)
1781 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);
1784 Mem_Free(pointfile);
1785 VectorCopy(leakorg, vecorg);
1786 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1788 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);
1789 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);
1790 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);
1795 CL_ParseParticleEffect
1797 Parse an effect out of the server message
1800 void CL_ParseParticleEffect (void)
1803 int i, count, msgcount, color;
1805 MSG_ReadVector(&cl_message, org, cls.protocol);
1806 for (i=0 ; i<3 ; i++)
1807 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1808 msgcount = MSG_ReadByte(&cl_message);
1809 color = MSG_ReadByte(&cl_message);
1811 if (msgcount == 255)
1816 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1821 CL_ParticleExplosion
1825 void CL_ParticleExplosion (const vec3_t org)
1831 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1832 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1834 if (cl_particles_quake.integer)
1836 for (i = 0;i < 1024;i++)
1842 color = particlepalette[ramp1[r]];
1843 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);
1847 color = particlepalette[ramp2[r]];
1848 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);
1854 i = CL_PointSuperContents(org);
1855 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1857 if (cl_particles.integer && cl_particles_bubbles.integer)
1858 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1859 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);
1863 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1865 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1872 VectorMA(org, 128, v2, v);
1873 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, collision_extendmovelength.value, true, false, NULL, false, false);
1875 while (k < 16 && trace.fraction < 0.1f);
1876 VectorSubtract(trace.endpos, org, v2);
1877 VectorScale(v2, 2.0f, v2);
1878 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);
1884 if (cl_particles_explosions_shell.integer)
1885 R_NewExplosion(org);
1890 CL_ParticleExplosion2
1894 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1897 if (!cl_particles.integer) return;
1899 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1901 k = particlepalette[colorStart + (i % colorLength)];
1902 if (cl_particles_quake.integer)
1903 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);
1905 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);
1909 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1912 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1913 if (cl_particles_sparks.integer)
1915 sparkcount *= cl_particles_quality.value;
1916 while(sparkcount-- > 0)
1917 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);
1921 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1924 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1925 if (cl_particles_smoke.integer)
1927 smokecount *= cl_particles_quality.value;
1928 while(smokecount-- > 0)
1929 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);
1933 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)
1937 if (!cl_particles.integer) return;
1938 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1940 count = (int)(count * cl_particles_quality.value);
1943 k = particlepalette[colorbase + (rand()&3)];
1944 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);
1948 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1951 float minz, maxz, lifetime = 30;
1953 if (!cl_particles.integer) return;
1954 if (dir[2] < 0) // falling
1956 minz = maxs[2] + dir[2] * 0.1;
1959 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1964 maxz = maxs[2] + dir[2] * 0.1;
1966 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1969 count = (int)(count * cl_particles_quality.value);
1974 if (!cl_particles_rain.integer) break;
1975 count *= 4; // ick, this should be in the mod or maps?
1979 k = particlepalette[colorbase + (rand()&3)];
1980 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1981 if (gamemode == GAME_GOODVSBAD2)
1982 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);
1984 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);
1988 if (!cl_particles_snow.integer) break;
1991 k = particlepalette[colorbase + (rand()&3)];
1992 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1993 if (gamemode == GAME_GOODVSBAD2)
1994 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);
1996 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);
2000 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
2004 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
2005 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
2006 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
2007 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
2008 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
2009 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
2011 #define PARTICLETEXTURESIZE 64
2012 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
2014 static unsigned char shadebubble(float dx, float dy, vec3_t light)
2018 dz = 1 - (dx*dx+dy*dy);
2019 if (dz > 0) // it does hit the sphere
2023 normal[0] = dx;normal[1] = dy;normal[2] = dz;
2024 VectorNormalize(normal);
2025 dot = DotProduct(normal, light);
2026 if (dot > 0.5) // interior reflection
2027 f += ((dot * 2) - 1);
2028 else if (dot < -0.5) // exterior reflection
2029 f += ((dot * -2) - 1);
2031 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
2032 VectorNormalize(normal);
2033 dot = DotProduct(normal, light);
2034 if (dot > 0.5) // interior reflection
2035 f += ((dot * 2) - 1);
2036 else if (dot < -0.5) // exterior reflection
2037 f += ((dot * -2) - 1);
2039 f += 16; // just to give it a haze so you can see the outline
2040 f = bound(0, f, 255);
2041 return (unsigned char) f;
2047 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
2048 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
2050 *basex = (texnum % particlefontcols) * particlefontcellwidth;
2051 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
2052 *width = particlefontcellwidth;
2053 *height = particlefontcellheight;
2056 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
2058 int basex, basey, w, h, y;
2059 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2060 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2061 Sys_Error("invalid particle texture size for autogenerating");
2062 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2063 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2066 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2069 float cx, cy, dx, dy, f, iradius;
2071 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2072 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2073 iradius = 1.0f / radius;
2074 alpha *= (1.0f / 255.0f);
2075 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2077 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2081 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2086 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2087 d[0] += (int)(f * (blue - d[0]));
2088 d[1] += (int)(f * (green - d[1]));
2089 d[2] += (int)(f * (red - d[2]));
2096 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2099 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2101 data[0] = bound(minb, data[0], maxb);
2102 data[1] = bound(ming, data[1], maxg);
2103 data[2] = bound(minr, data[2], maxr);
2108 static void particletextureinvert(unsigned char *data)
2111 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2113 data[0] = 255 - data[0];
2114 data[1] = 255 - data[1];
2115 data[2] = 255 - data[2];
2119 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2120 static void R_InitBloodTextures (unsigned char *particletexturedata)
2123 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2124 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2127 for (i = 0;i < 8;i++)
2129 memset(data, 255, datasize);
2130 for (k = 0;k < 24;k++)
2131 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2132 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2133 particletextureinvert(data);
2134 setuptex(tex_bloodparticle[i], data, particletexturedata);
2138 for (i = 0;i < 8;i++)
2140 memset(data, 255, datasize);
2142 for (j = 1;j < 10;j++)
2143 for (k = min(j, m - 1);k < m;k++)
2144 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2145 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2146 particletextureinvert(data);
2147 setuptex(tex_blooddecal[i], data, particletexturedata);
2153 //uncomment this to make engine save out particle font to a tga file when run
2154 //#define DUMPPARTICLEFONT
2156 static void R_InitParticleTexture (void)
2158 int x, y, d, i, k, m;
2159 int basex, basey, w, h;
2160 float dx, dy, f, s1, t1, s2, t2;
2163 fs_offset_t filesize;
2164 char texturename[MAX_QPATH];
2167 // a note: decals need to modulate (multiply) the background color to
2168 // properly darken it (stain), and they need to be able to alpha fade,
2169 // this is a very difficult challenge because it means fading to white
2170 // (no change to background) rather than black (darkening everything
2171 // behind the whole decal polygon), and to accomplish this the texture is
2172 // inverted (dark red blood on white background becomes brilliant cyan
2173 // and white on black background) so we can alpha fade it to black, then
2174 // we invert it again during the blendfunc to make it work...
2176 #ifndef DUMPPARTICLEFONT
2177 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2180 particlefonttexture = decalskinframe->base;
2181 // TODO maybe allow custom grid size?
2182 particlefontwidth = image_width;
2183 particlefontheight = image_height;
2184 particlefontcellwidth = image_width / 8;
2185 particlefontcellheight = image_height / 8;
2186 particlefontcols = 8;
2187 particlefontrows = 8;
2192 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2193 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2194 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2195 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2196 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2198 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2199 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2200 particlefontcols = 8;
2201 particlefontrows = 8;
2203 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2206 for (i = 0;i < 8;i++)
2208 memset(data, 255, datasize);
2211 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2212 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2214 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2216 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2217 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2219 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2220 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2222 d = (int)(d * (1-(dx*dx+dy*dy)));
2223 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2224 d = bound(0, d, 255);
2225 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2232 setuptex(tex_smoke[i], data, particletexturedata);
2236 memset(data, 255, datasize);
2237 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2239 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2240 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2242 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2243 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2244 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2247 setuptex(tex_rainsplash, data, particletexturedata);
2250 memset(data, 255, datasize);
2251 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2253 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2254 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2256 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2257 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2258 d = bound(0, d, 255);
2259 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2262 setuptex(tex_particle, data, particletexturedata);
2265 memset(data, 255, datasize);
2266 light[0] = 1;light[1] = 1;light[2] = 1;
2267 VectorNormalize(light);
2268 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2270 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2271 // stretch upper half of bubble by +50% and shrink lower half by -50%
2272 // (this gives an elongated teardrop shape)
2274 dy = (dy - 0.5f) * 2.0f;
2276 dy = (dy - 0.5f) / 1.5f;
2277 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2279 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2280 // shrink bubble width to half
2282 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2285 setuptex(tex_raindrop, data, particletexturedata);
2288 memset(data, 255, datasize);
2289 light[0] = 1;light[1] = 1;light[2] = 1;
2290 VectorNormalize(light);
2291 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2293 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2294 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2296 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2297 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2300 setuptex(tex_bubble, data, particletexturedata);
2302 // Blood particles and blood decals
2303 R_InitBloodTextures (particletexturedata);
2306 for (i = 0;i < 8;i++)
2308 memset(data, 255, datasize);
2309 for (k = 0;k < 12;k++)
2310 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2311 for (k = 0;k < 3;k++)
2312 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2313 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2314 particletextureinvert(data);
2315 setuptex(tex_bulletdecal[i], data, particletexturedata);
2318 #ifdef DUMPPARTICLEFONT
2319 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2322 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2323 particlefonttexture = decalskinframe->base;
2325 Mem_Free(particletexturedata);
2330 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2332 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2333 particletexture[i].texture = particlefonttexture;
2334 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2335 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2336 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2337 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2340 #ifndef DUMPPARTICLEFONT
2341 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2342 if (!particletexture[tex_beam].texture)
2345 unsigned char noise3[64][64], data2[64][16][4];
2347 fractalnoise(&noise3[0][0], 64, 4);
2349 for (y = 0;y < 64;y++)
2351 dy = (y - 0.5f*64) / (64*0.5f-1);
2352 for (x = 0;x < 16;x++)
2354 dx = (x - 0.5f*16) / (16*0.5f-2);
2355 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2356 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2357 data2[y][x][3] = 255;
2361 #ifdef DUMPPARTICLEFONT
2362 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2364 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2366 particletexture[tex_beam].s1 = 0;
2367 particletexture[tex_beam].t1 = 0;
2368 particletexture[tex_beam].s2 = 1;
2369 particletexture[tex_beam].t2 = 1;
2371 // now load an texcoord/texture override file
2372 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2379 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2381 if(!strcmp(com_token, "\n"))
2382 continue; // empty line
2383 i = atoi(com_token);
2391 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2393 strlcpy(texturename, com_token, sizeof(texturename));
2394 s1 = atof(com_token);
2395 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2398 t1 = atof(com_token);
2399 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2401 s2 = atof(com_token);
2402 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2404 t2 = atof(com_token);
2405 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2406 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2407 strlcpy(texturename, com_token, sizeof(texturename));
2414 if (!texturename[0])
2416 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2419 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2421 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2424 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2427 // R_SkinFrame_LoadExternal already complained
2430 particletexture[i].texture = sf->base;
2431 particletexture[i].s1 = s1;
2432 particletexture[i].t1 = t1;
2433 particletexture[i].s2 = s2;
2434 particletexture[i].t2 = t2;
2440 static void r_part_start(void)
2443 // generate particlepalette for convenience from the main one
2444 for (i = 0;i < 256;i++)
2445 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2446 particletexturepool = R_AllocTexturePool();
2447 R_InitParticleTexture ();
2448 CL_Particles_LoadEffectInfo(NULL);
2451 static void r_part_shutdown(void)
2453 R_FreeTexturePool(&particletexturepool);
2456 static void r_part_newmap(void)
2459 R_SkinFrame_MarkUsed(decalskinframe);
2460 CL_Particles_LoadEffectInfo(NULL);
2463 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2464 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2466 void R_Particles_Init (void)
2469 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2471 particle_elements[i*6+0] = i*4+0;
2472 particle_elements[i*6+1] = i*4+1;
2473 particle_elements[i*6+2] = i*4+2;
2474 particle_elements[i*6+3] = i*4+0;
2475 particle_elements[i*6+4] = i*4+2;
2476 particle_elements[i*6+5] = i*4+3;
2479 Cvar_RegisterVariable(&r_drawparticles);
2480 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2481 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2482 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2483 Cvar_RegisterVariable(&r_drawdecals);
2484 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2485 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2488 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2490 int surfacelistindex;
2492 float *v3f, *t2f, *c4f;
2493 particletexture_t *tex;
2494 vec_t right[3], up[3], size, ca;
2495 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2497 RSurf_ActiveWorldEntity();
2499 r_refdef.stats[r_stat_drawndecals] += numsurfaces;
2500 // R_Mesh_ResetTextureState();
2501 GL_DepthMask(false);
2502 GL_DepthRange(0, 1);
2503 GL_PolygonOffset(0, 0);
2505 GL_CullFace(GL_NONE);
2507 // generate all the vertices at once
2508 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2510 d = cl.decals + surfacelist[surfacelistindex];
2513 c4f = particle_color4f + 16*surfacelistindex;
2514 ca = d->alpha * alphascale;
2515 // ensure alpha multiplier saturates properly
2516 if (ca > 1.0f / 256.0f)
2518 if (r_refdef.fogenabled)
2519 ca *= RSurf_FogVertex(d->org);
2520 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2521 Vector4Copy(c4f, c4f + 4);
2522 Vector4Copy(c4f, c4f + 8);
2523 Vector4Copy(c4f, c4f + 12);
2525 // calculate vertex positions
2526 size = d->size * cl_particles_size.value;
2527 VectorVectors(d->normal, right, up);
2528 VectorScale(right, size, right);
2529 VectorScale(up, size, up);
2530 v3f = particle_vertex3f + 12*surfacelistindex;
2531 v3f[ 0] = d->org[0] - right[0] - up[0];
2532 v3f[ 1] = d->org[1] - right[1] - up[1];
2533 v3f[ 2] = d->org[2] - right[2] - up[2];
2534 v3f[ 3] = d->org[0] - right[0] + up[0];
2535 v3f[ 4] = d->org[1] - right[1] + up[1];
2536 v3f[ 5] = d->org[2] - right[2] + up[2];
2537 v3f[ 6] = d->org[0] + right[0] + up[0];
2538 v3f[ 7] = d->org[1] + right[1] + up[1];
2539 v3f[ 8] = d->org[2] + right[2] + up[2];
2540 v3f[ 9] = d->org[0] + right[0] - up[0];
2541 v3f[10] = d->org[1] + right[1] - up[1];
2542 v3f[11] = d->org[2] + right[2] - up[2];
2544 // calculate texcoords
2545 tex = &particletexture[d->texnum];
2546 t2f = particle_texcoord2f + 8*surfacelistindex;
2547 t2f[0] = tex->s1;t2f[1] = tex->t2;
2548 t2f[2] = tex->s1;t2f[3] = tex->t1;
2549 t2f[4] = tex->s2;t2f[5] = tex->t1;
2550 t2f[6] = tex->s2;t2f[7] = tex->t2;
2553 // now render the decals all at once
2554 // (this assumes they all use one particle font texture!)
2555 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2556 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2557 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2558 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2561 void R_DrawDecals (void)
2564 int drawdecals = r_drawdecals.integer;
2569 unsigned int killsequence = cl.decalsequence - bound(0, (unsigned int) cl_decals_max.integer, cl.decalsequence);
2571 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2572 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2574 // LordHavoc: early out conditions
2578 decalfade = frametime * 256 / cl_decals_fadetime.value;
2579 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2580 drawdist2 = drawdist2*drawdist2;
2582 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2584 if (!decal->typeindex)
2587 if (killsequence > decal->decalsequence)
2590 if (cl.time > decal->time2 + cl_decals_time.value)
2592 decal->alpha -= decalfade;
2593 if (decal->alpha <= 0)
2599 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2601 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2602 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2608 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2614 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))
2615 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2618 decal->typeindex = 0;
2619 if (cl.free_decal > i)
2623 // reduce cl.num_decals if possible
2624 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2627 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2629 decal_t *olddecals = cl.decals;
2630 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2631 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2632 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2633 Mem_Free(olddecals);
2636 r_refdef.stats[r_stat_totaldecals] = cl.num_decals;
2639 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2641 vec3_t vecorg, vecvel, baseright, baseup;
2642 int surfacelistindex;
2643 int batchstart, batchcount;
2644 const particle_t *p;
2646 rtexture_t *texture;
2647 float *v3f, *t2f, *c4f;
2648 particletexture_t *tex;
2649 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2650 // float ambient[3], diffuse[3], diffusenormal[3];
2651 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2652 vec4_t colormultiplier;
2653 float minparticledist_start, minparticledist_end;
2656 RSurf_ActiveWorldEntity();
2658 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));
2660 r_refdef.stats[r_stat_particles] += numsurfaces;
2661 // R_Mesh_ResetTextureState();
2662 GL_DepthMask(false);
2663 GL_DepthRange(0, 1);
2664 GL_PolygonOffset(0, 0);
2666 GL_CullFace(GL_NONE);
2668 spintime = r_refdef.scene.time;
2670 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2671 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2672 dofade = (minparticledist_start < minparticledist_end);
2674 // first generate all the vertices at once
2675 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2677 p = cl.particles + surfacelist[surfacelistindex];
2679 blendmode = (pblend_t)p->blendmode;
2681 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2682 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2683 alpha = palpha * colormultiplier[3];
2684 // ensure alpha multiplier saturates properly
2690 case PBLEND_INVALID:
2692 // additive and modulate can just fade out in fog (this is correct)
2693 if (r_refdef.fogenabled)
2694 alpha *= RSurf_FogVertex(p->org);
2695 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2696 alpha *= 1.0f / 256.0f;
2697 c4f[0] = p->color[0] * alpha;
2698 c4f[1] = p->color[1] * alpha;
2699 c4f[2] = p->color[2] * alpha;
2703 // additive and modulate can just fade out in fog (this is correct)
2704 if (r_refdef.fogenabled)
2705 alpha *= RSurf_FogVertex(p->org);
2706 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2707 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2708 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2709 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2713 c4f[0] = p->color[0] * colormultiplier[0];
2714 c4f[1] = p->color[1] * colormultiplier[1];
2715 c4f[2] = p->color[2] * colormultiplier[2];
2717 // note: lighting is not cheap!
2718 if (particletype[p->typeindex].lighting)
2720 vecorg[0] = p->org[0];
2721 vecorg[1] = p->org[1];
2722 vecorg[2] = p->org[2];
2723 R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2725 // mix in the fog color
2726 if (r_refdef.fogenabled)
2728 fog = RSurf_FogVertex(p->org);
2730 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2731 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2732 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2734 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2735 VectorScale(c4f, alpha, c4f);
2738 // copy the color into the other three vertices
2739 Vector4Copy(c4f, c4f + 4);
2740 Vector4Copy(c4f, c4f + 8);
2741 Vector4Copy(c4f, c4f + 12);
2743 size = p->size * cl_particles_size.value;
2744 tex = &particletexture[p->texnum];
2745 switch(p->orientation)
2747 // case PARTICLE_INVALID:
2748 case PARTICLE_BILLBOARD:
2749 if (p->angle + p->spin)
2751 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2752 spinsin = sin(spinrad) * size;
2753 spincos = cos(spinrad) * size;
2754 spinm1 = -p->stretch * spincos;
2757 spinm4 = -p->stretch * spincos;
2758 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2759 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2763 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2764 VectorScale(r_refdef.view.up, size, up);
2767 v3f[ 0] = p->org[0] - right[0] - up[0];
2768 v3f[ 1] = p->org[1] - right[1] - up[1];
2769 v3f[ 2] = p->org[2] - right[2] - up[2];
2770 v3f[ 3] = p->org[0] - right[0] + up[0];
2771 v3f[ 4] = p->org[1] - right[1] + up[1];
2772 v3f[ 5] = p->org[2] - right[2] + up[2];
2773 v3f[ 6] = p->org[0] + right[0] + up[0];
2774 v3f[ 7] = p->org[1] + right[1] + up[1];
2775 v3f[ 8] = p->org[2] + right[2] + up[2];
2776 v3f[ 9] = p->org[0] + right[0] - up[0];
2777 v3f[10] = p->org[1] + right[1] - up[1];
2778 v3f[11] = p->org[2] + right[2] - up[2];
2779 t2f[0] = tex->s1;t2f[1] = tex->t2;
2780 t2f[2] = tex->s1;t2f[3] = tex->t1;
2781 t2f[4] = tex->s2;t2f[5] = tex->t1;
2782 t2f[6] = tex->s2;t2f[7] = tex->t2;
2784 case PARTICLE_ORIENTED_DOUBLESIDED:
2785 vecvel[0] = p->vel[0];
2786 vecvel[1] = p->vel[1];
2787 vecvel[2] = p->vel[2];
2788 VectorVectors(vecvel, baseright, baseup);
2789 if (p->angle + p->spin)
2791 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2792 spinsin = sin(spinrad) * size;
2793 spincos = cos(spinrad) * size;
2794 spinm1 = p->stretch * spincos;
2797 spinm4 = p->stretch * spincos;
2798 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2799 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2803 VectorScale(baseright, size * p->stretch, right);
2804 VectorScale(baseup, size, up);
2806 v3f[ 0] = p->org[0] - right[0] - up[0];
2807 v3f[ 1] = p->org[1] - right[1] - up[1];
2808 v3f[ 2] = p->org[2] - right[2] - up[2];
2809 v3f[ 3] = p->org[0] - right[0] + up[0];
2810 v3f[ 4] = p->org[1] - right[1] + up[1];
2811 v3f[ 5] = p->org[2] - right[2] + up[2];
2812 v3f[ 6] = p->org[0] + right[0] + up[0];
2813 v3f[ 7] = p->org[1] + right[1] + up[1];
2814 v3f[ 8] = p->org[2] + right[2] + up[2];
2815 v3f[ 9] = p->org[0] + right[0] - up[0];
2816 v3f[10] = p->org[1] + right[1] - up[1];
2817 v3f[11] = p->org[2] + right[2] - up[2];
2818 t2f[0] = tex->s1;t2f[1] = tex->t2;
2819 t2f[2] = tex->s1;t2f[3] = tex->t1;
2820 t2f[4] = tex->s2;t2f[5] = tex->t1;
2821 t2f[6] = tex->s2;t2f[7] = tex->t2;
2823 case PARTICLE_SPARK:
2824 len = VectorLength(p->vel);
2825 VectorNormalize2(p->vel, up);
2826 lenfactor = p->stretch * 0.04 * len;
2827 if(lenfactor < size * 0.5)
2828 lenfactor = size * 0.5;
2829 VectorMA(p->org, -lenfactor, up, v);
2830 VectorMA(p->org, lenfactor, up, up2);
2831 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2832 t2f[0] = tex->s1;t2f[1] = tex->t2;
2833 t2f[2] = tex->s1;t2f[3] = tex->t1;
2834 t2f[4] = tex->s2;t2f[5] = tex->t1;
2835 t2f[6] = tex->s2;t2f[7] = tex->t2;
2837 case PARTICLE_VBEAM:
2838 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2839 VectorSubtract(p->vel, p->org, up);
2840 VectorNormalize(up);
2841 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2842 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2843 t2f[0] = tex->s2;t2f[1] = v[0];
2844 t2f[2] = tex->s1;t2f[3] = v[0];
2845 t2f[4] = tex->s1;t2f[5] = v[1];
2846 t2f[6] = tex->s2;t2f[7] = v[1];
2848 case PARTICLE_HBEAM:
2849 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2850 VectorSubtract(p->vel, p->org, up);
2851 VectorNormalize(up);
2852 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2853 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2854 t2f[0] = v[0];t2f[1] = tex->t1;
2855 t2f[2] = v[0];t2f[3] = tex->t2;
2856 t2f[4] = v[1];t2f[5] = tex->t2;
2857 t2f[6] = v[1];t2f[7] = tex->t1;
2862 // now render batches of particles based on blendmode and texture
2863 blendmode = PBLEND_INVALID;
2867 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2868 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2870 p = cl.particles + surfacelist[surfacelistindex];
2872 if (texture != particletexture[p->texnum].texture)
2874 texture = particletexture[p->texnum].texture;
2875 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2878 if (p->blendmode == PBLEND_INVMOD)
2880 // inverse modulate blend - group these
2881 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2882 // iterate until we find a change in settings
2883 batchstart = surfacelistindex++;
2884 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2886 p = cl.particles + surfacelist[surfacelistindex];
2887 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2893 // additive or alpha blend - group these
2894 // (we can group these because we premultiplied the texture alpha)
2895 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2896 // iterate until we find a change in settings
2897 batchstart = surfacelistindex++;
2898 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2900 p = cl.particles + surfacelist[surfacelistindex];
2901 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2906 batchcount = surfacelistindex - batchstart;
2907 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2911 void R_DrawParticles (void)
2914 int drawparticles = r_drawparticles.integer;
2915 float minparticledist_start;
2917 float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2923 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2924 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2926 // LordHavoc: early out conditions
2927 if (!cl.num_particles)
2930 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2931 gravity = frametime * cl.movevars_gravity;
2932 update = frametime > 0;
2933 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2934 drawdist2 = drawdist2*drawdist2;
2936 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2940 if (cl.free_particle > i)
2941 cl.free_particle = i;
2947 if (p->delayedspawn > cl.time)
2950 p->size += p->sizeincrease * frametime;
2951 p->alpha -= p->alphafade * frametime;
2953 if (p->alpha <= 0 || p->die <= cl.time)
2956 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2958 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2960 if (p->typeindex == pt_blood)
2961 p->size += frametime * 8;
2963 p->vel[2] -= p->gravity * gravity;
2964 f = 1.0f - min(p->liquidfriction * frametime, 1);
2965 VectorScale(p->vel, f, p->vel);
2969 p->vel[2] -= p->gravity * gravity;
2972 f = 1.0f - min(p->airfriction * frametime, 1);
2973 VectorScale(p->vel, f, p->vel);
2977 VectorCopy(p->org, oldorg);
2978 VectorMA(p->org, frametime, p->vel, p->org);
2979 // if (p->bounce && cl.time >= p->delayedcollisions)
2980 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2982 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);
2983 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2984 // or if the trace hit something flagged as NOIMPACT
2985 // then remove the particle
2986 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2988 VectorCopy(trace.endpos, p->org);
2989 // react if the particle hit something
2990 if (trace.fraction < 1)
2992 VectorCopy(trace.endpos, p->org);
2994 if (p->staintexnum >= 0)
2996 // blood - splash on solid
2997 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
3000 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
3001 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
3002 if (cl_decals.integer)
3004 // create a decal for the blood splat
3005 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
3006 if (cl_decals_newsystem_bloodsmears.integer)
3008 VectorCopy(p->vel, decaldir);
3009 VectorNormalize(decaldir);
3012 VectorCopy(trace.plane.normal, decaldir);
3013 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
3018 if (p->typeindex == pt_blood)
3020 // blood - splash on solid
3021 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
3023 if(p->staintexnum == -1) // staintex < -1 means no stains at all
3025 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)));
3026 if (cl_decals.integer)
3028 // create a decal for the blood splat
3029 if (cl_decals_newsystem_bloodsmears.integer)
3031 VectorCopy(p->vel, decaldir);
3032 VectorNormalize(decaldir);
3035 VectorCopy(trace.plane.normal, decaldir);
3036 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);
3041 else if (p->bounce < 0)
3043 // bounce -1 means remove on impact
3048 // anything else - bounce off solid
3049 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
3050 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
3055 if (VectorLength2(p->vel) < 0.03)
3057 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
3059 VectorClear(p->vel);
3063 if (p->typeindex != pt_static)
3065 switch (p->typeindex)
3067 case pt_entityparticle:
3068 // particle that removes itself after one rendered frame
3075 a = CL_PointSuperContents(p->org);
3076 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
3080 a = CL_PointSuperContents(p->org);
3081 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
3085 a = CL_PointSuperContents(p->org);
3086 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3090 if (cl.time > p->time2)
3093 p->time2 = cl.time + (rand() & 3) * 0.1;
3094 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3095 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3097 a = CL_PointSuperContents(p->org);
3098 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3106 else if (p->delayedspawn > cl.time)
3110 // don't render particles too close to the view (they chew fillrate)
3111 // also don't render particles behind the view (useless)
3112 // further checks to cull to the frustum would be too slow here
3113 switch(p->typeindex)
3116 // beams have no culling
3117 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3120 if(cl_particles_visculling.integer)
3121 if (!r_refdef.viewcache.world_novis)
3122 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3124 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3126 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3129 // anything else just has to be in front of the viewer and visible at this distance
3130 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3131 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3138 if (cl.free_particle > i)
3139 cl.free_particle = i;
3142 // reduce cl.num_particles if possible
3143 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3146 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3148 particle_t *oldparticles = cl.particles;
3149 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3150 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3151 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3152 Mem_Free(oldparticles);