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 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
33 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
38 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
51 typedef struct particleeffectinfo_s
53 int effectnameindex; // which effect this belongs to
54 // PARTICLEEFFECT_* bits
56 // blood effects may spawn very few particles, so proper fraction-overflow
57 // handling is very important, this variable keeps track of the fraction
58 double particleaccumulator;
59 // the math is: countabsolute + requestedcount * countmultiplier * quality
60 // absolute number of particles to spawn, often used for decals
61 // (unaffected by quality and requestedcount)
63 // multiplier for the number of particles CL_ParticleEffect was told to
64 // spawn, most effects do not really have a count and hence use 1, so
65 // this is often the actual count to spawn, not merely a multiplier
66 float countmultiplier;
67 // if > 0 this causes the particle to spawn in an evenly spaced line from
68 // originmins to originmaxs (causing them to describe a trail, not a box)
70 // type of particle to spawn (defines some aspects of behavior)
72 // blending mode used on this particle type
74 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
75 porientation_t orientation;
76 // range of colors to choose from in hex RRGGBB (like HTML color tags),
77 // randomly interpolated at spawn
78 unsigned int color[2];
79 // a random texture is chosen in this range (note the second value is one
80 // past the last choosable, so for example 8,16 chooses any from 8 up and
82 // if start and end of the range are the same, no randomization is done
84 // range of size values randomly chosen when spawning, plus size increase over time
86 // range of alpha values randomly chosen when spawning, plus alpha fade
88 // how long the particle should live (note it is also removed if alpha drops to 0)
90 // how much gravity affects this particle (negative makes it fly up!)
92 // how much bounce the particle has when it hits a surface
93 // if negative the particle is removed on impact
95 // if in air this friction is applied
96 // if negative the particle accelerates
98 // if in liquid (water/slime/lava) this friction is applied
99 // if negative the particle accelerates
100 float liquidfriction;
101 // these offsets are added to the values given to particleeffect(), and
102 // then an ellipsoid-shaped jitter is added as defined by these
103 // (they are the 3 radii)
105 // stretch velocity factor (used for sparks)
106 float originoffset[3];
107 float velocityoffset[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 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
121 particleeffectinfo_t;
123 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
128 static int particlepalette[256];
130 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
131 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
132 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
133 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
134 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
135 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
136 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
137 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
138 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
139 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
140 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
141 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
142 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
143 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
144 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
145 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
146 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
147 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
148 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
149 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
150 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
151 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
152 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
153 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
154 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
155 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
156 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
157 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
158 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
159 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
160 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
161 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
164 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
165 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
166 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
168 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
170 // particletexture_t is a rectangle in the particlefonttexture
171 typedef struct particletexture_s
174 float s1, t1, s2, t2;
178 static rtexturepool_t *particletexturepool;
179 static rtexture_t *particlefonttexture;
180 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
181 skinframe_t *decalskinframe;
183 // texture numbers in particle font
184 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
185 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
186 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
187 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
188 static const int tex_rainsplash = 32;
189 static const int tex_particle = 63;
190 static const int tex_bubble = 62;
191 static const int tex_raindrop = 61;
192 static const int tex_beam = 60;
194 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
195 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
196 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
197 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
198 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
199 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
200 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
201 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
202 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
203 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
204 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
205 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
206 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
207 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
208 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
209 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
210 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
211 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
212 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
213 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
214 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
215 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
216 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
217 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "0", "enables new advanced decal system"};
218 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)"};
219 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
220 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
223 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
229 particleeffectinfo_t *info = NULL;
230 const char *text = textstart;
232 effectinfoindex = -1;
233 for (linenumber = 1;;linenumber++)
236 for (arrayindex = 0;arrayindex < 16;arrayindex++)
237 argv[arrayindex][0] = 0;
240 if (!COM_ParseToken_Simple(&text, true, false))
242 if (!strcmp(com_token, "\n"))
246 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
252 #define checkparms(n) if (argc != (n)) {Con_Printf("effectinfo.txt:%i: error while parsing: %s given %i parameters, should be %i parameters\n", linenumber, argv[0], argc, (n));break;}
253 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
254 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
255 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
256 #define readfloat(var) checkparms(2);var = atof(argv[1])
257 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
258 if (!strcmp(argv[0], "effect"))
263 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
265 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
268 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
270 if (particleeffectname[effectnameindex][0])
272 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
277 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
281 // if we run out of names, abort
282 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
284 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
287 info = particleeffectinfo + effectinfoindex;
288 info->effectnameindex = effectnameindex;
289 info->particletype = pt_alphastatic;
290 info->blendmode = particletype[info->particletype].blendmode;
291 info->orientation = particletype[info->particletype].orientation;
292 info->tex[0] = tex_particle;
293 info->tex[1] = tex_particle;
294 info->color[0] = 0xFFFFFF;
295 info->color[1] = 0xFFFFFF;
299 info->alpha[1] = 256;
300 info->alpha[2] = 256;
301 info->time[0] = 9999;
302 info->time[1] = 9999;
303 VectorSet(info->lightcolor, 1, 1, 1);
304 info->lightshadow = true;
305 info->lighttime = 9999;
306 info->stretchfactor = 1;
307 info->staincolor[0] = (unsigned int)-1;
308 info->staincolor[1] = (unsigned int)-1;
309 info->staintex[0] = -1;
310 info->staintex[1] = -1;
312 else if (info == NULL)
314 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
317 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
318 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
319 else if (!strcmp(argv[0], "type"))
322 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
323 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
324 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
325 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
326 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
327 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
328 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
329 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
330 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
331 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
332 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
333 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
334 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
335 info->blendmode = particletype[info->particletype].blendmode;
336 info->orientation = particletype[info->particletype].orientation;
338 else if (!strcmp(argv[0], "blend"))
341 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
342 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
343 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
344 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
346 else if (!strcmp(argv[0], "orientation"))
349 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
350 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
351 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
352 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
353 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
355 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
356 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
357 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
358 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
359 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
360 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
361 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
362 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
363 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
364 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
365 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
366 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
367 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
368 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
369 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
370 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
371 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
372 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
373 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
374 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
375 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
376 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
377 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
378 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
379 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
380 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
381 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
382 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1;}
384 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
393 int CL_ParticleEffectIndexForName(const char *name)
396 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
397 if (!strcmp(particleeffectname[i], name))
402 const char *CL_ParticleEffectNameForIndex(int i)
404 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
406 return particleeffectname[i];
409 // MUST match effectnameindex_t in client.h
410 static const char *standardeffectnames[EFFECT_TOTAL] =
434 "TE_TEI_BIGEXPLOSION",
450 void CL_Particles_LoadEffectInfo(void)
453 unsigned char *filedata;
454 fs_offset_t filesize;
455 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
456 memset(particleeffectname, 0, sizeof(particleeffectname));
457 for (i = 0;i < EFFECT_TOTAL;i++)
458 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
459 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
462 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
472 void CL_ReadPointFile_f (void);
473 void CL_Particles_Init (void)
475 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)");
476 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
478 Cvar_RegisterVariable (&cl_particles);
479 Cvar_RegisterVariable (&cl_particles_quality);
480 Cvar_RegisterVariable (&cl_particles_alpha);
481 Cvar_RegisterVariable (&cl_particles_size);
482 Cvar_RegisterVariable (&cl_particles_quake);
483 Cvar_RegisterVariable (&cl_particles_blood);
484 Cvar_RegisterVariable (&cl_particles_blood_alpha);
485 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
486 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
487 Cvar_RegisterVariable (&cl_particles_explosions_shell);
488 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
489 Cvar_RegisterVariable (&cl_particles_rain);
490 Cvar_RegisterVariable (&cl_particles_snow);
491 Cvar_RegisterVariable (&cl_particles_smoke);
492 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
493 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
494 Cvar_RegisterVariable (&cl_particles_sparks);
495 Cvar_RegisterVariable (&cl_particles_bubbles);
496 Cvar_RegisterVariable (&cl_particles_visculling);
497 Cvar_RegisterVariable (&cl_decals);
498 Cvar_RegisterVariable (&cl_decals_visculling);
499 Cvar_RegisterVariable (&cl_decals_time);
500 Cvar_RegisterVariable (&cl_decals_fadetime);
501 Cvar_RegisterVariable (&cl_decals_newsystem);
502 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
503 Cvar_RegisterVariable (&cl_decals_models);
504 Cvar_RegisterVariable (&cl_decals_bias);
507 void CL_Particles_Shutdown (void)
511 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
512 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
514 // list of all 26 parameters:
515 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
516 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
517 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
518 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
519 // palpha - opacity of particle as 0-255 (can be more than 255)
520 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
521 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
522 // pgravity - how much effect gravity has on the particle (0-1)
523 // 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
524 // px,py,pz - starting origin of particle
525 // pvx,pvy,pvz - starting velocity of particle
526 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
527 // blendmode - one of the PBLEND_ values
528 // orientation - one of the PARTICLE_ values
529 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
530 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
531 particle_t *CL_NewParticle(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)
536 if (!cl_particles.integer)
538 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
539 if (cl.free_particle >= cl.max_particles)
542 lifetime = palpha / min(1, palphafade);
543 part = &cl.particles[cl.free_particle++];
544 if (cl.num_particles < cl.free_particle)
545 cl.num_particles = cl.free_particle;
546 memset(part, 0, sizeof(*part));
547 part->typeindex = ptypeindex;
548 part->blendmode = blendmode;
549 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
551 particletexture_t *tex = &particletexture[ptex];
552 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
553 part->orientation = PARTICLE_VBEAM;
555 part->orientation = PARTICLE_HBEAM;
558 part->orientation = orientation;
559 l2 = (int)lhrandom(0.5, 256.5);
561 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
562 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
563 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
564 part->staintexnum = staintex;
565 if(staincolor1 >= 0 && staincolor2 >= 0)
567 l2 = (int)lhrandom(0.5, 256.5);
569 if(blendmode == PBLEND_INVMOD)
571 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
572 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
573 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
577 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
578 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
579 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
581 if(r > 0xFF) r = 0xFF;
582 if(g > 0xFF) g = 0xFF;
583 if(b > 0xFF) b = 0xFF;
587 r = part->color[0]; // -1 is shorthand for stain = particle color
591 part->staincolor = r * 65536 + g * 256 + b;
594 part->sizeincrease = psizeincrease;
595 part->alpha = palpha;
596 part->alphafade = palphafade;
597 part->gravity = pgravity;
598 part->bounce = pbounce;
599 part->stretch = stretch;
601 part->org[0] = px + originjitter * v[0];
602 part->org[1] = py + originjitter * v[1];
603 part->org[2] = pz + originjitter * v[2];
604 part->vel[0] = pvx + velocityjitter * v[0];
605 part->vel[1] = pvy + velocityjitter * v[1];
606 part->vel[2] = pvz + velocityjitter * v[2];
608 part->airfriction = pairfriction;
609 part->liquidfriction = pliquidfriction;
610 part->die = cl.time + lifetime;
611 part->delayedcollisions = 0;
612 part->qualityreduction = pqualityreduction;
613 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
614 if (part->typeindex == pt_rain)
618 float lifetime = part->die - cl.time;
621 // turn raindrop into simple spark and create delayedspawn splash effect
622 part->typeindex = pt_spark;
624 VectorMA(part->org, lifetime, part->vel, endvec);
625 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
626 part->die = cl.time + lifetime * trace.fraction;
627 part2 = CL_NewParticle(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);
630 part2->delayedspawn = part->die;
631 part2->die += part->die - cl.time;
632 for (i = rand() & 7;i < 10;i++)
634 part2 = CL_NewParticle(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);
637 part2->delayedspawn = part->die;
638 part2->die += part->die - cl.time;
643 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
645 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
648 VectorMA(part->org, lifetime, part->vel, endvec);
649 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
650 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
656 static void CL_ImmediateBloodStain(particle_t *part)
661 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
662 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
664 VectorCopy(part->vel, v);
666 staintex = part->staintexnum;
667 R_DecalSystem_SplatEntities(part->org, v, 1-((part->staincolor>>16)&255)*(1.0f/255.0f), 1-((part->staincolor>>8)&255)*(1.0f/255.0f), 1-((part->staincolor)&255)*(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);
670 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
671 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
673 VectorCopy(part->vel, v);
675 staintex = tex_blooddecal[rand()&7];
676 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);
680 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
684 entity_render_t *ent = &cl.entities[hitent].render;
685 unsigned char color[3];
686 if (!cl_decals.integer)
688 if (!ent->allowdecals)
691 l2 = (int)lhrandom(0.5, 256.5);
693 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
694 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
695 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
697 if (cl_decals_newsystem.integer)
699 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);
703 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
704 if (cl.free_decal >= cl.max_decals)
706 decal = &cl.decals[cl.free_decal++];
707 if (cl.num_decals < cl.free_decal)
708 cl.num_decals = cl.free_decal;
709 memset(decal, 0, sizeof(*decal));
710 decal->typeindex = pt_decal;
711 decal->texnum = texnum;
712 VectorMA(org, cl_decals_bias.value, normal, decal->org);
713 VectorCopy(normal, decal->normal);
715 decal->alpha = alpha;
716 decal->time2 = cl.time;
717 decal->color[0] = color[0];
718 decal->color[1] = color[1];
719 decal->color[2] = color[2];
720 decal->owner = hitent;
721 decal->clusterindex = -1000; // no vis culling unless we're sure
724 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
725 decal->ownermodel = cl.entities[decal->owner].render.model;
726 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
727 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
731 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
733 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
735 decal->clusterindex = leaf->clusterindex;
740 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
743 float bestfrac, bestorg[3], bestnormal[3];
745 int besthitent = 0, hitent;
748 for (i = 0;i < 32;i++)
751 VectorMA(org, maxdist, org2, org2);
752 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
753 // take the closest trace result that doesn't end up hitting a NOMARKS
754 // surface (sky for example)
755 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
757 bestfrac = trace.fraction;
759 VectorCopy(trace.endpos, bestorg);
760 VectorCopy(trace.plane.normal, bestnormal);
764 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
767 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
768 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
769 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)
772 matrix4x4_t tempmatrix;
774 VectorLerp(originmins, 0.5, originmaxs, center);
775 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
776 if (effectnameindex == EFFECT_SVC_PARTICLE)
778 if (cl_particles.integer)
780 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
782 CL_ParticleExplosion(center);
783 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
784 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
787 count *= cl_particles_quality.value;
788 for (;count > 0;count--)
790 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
791 CL_NewParticle(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);
796 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
797 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
798 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
799 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
800 else if (effectnameindex == EFFECT_TE_SPIKE)
802 if (cl_particles_bulletimpacts.integer)
804 if (cl_particles_quake.integer)
806 if (cl_particles_smoke.integer)
807 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
811 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
812 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
813 CL_NewParticle(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);
817 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
818 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
820 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
822 if (cl_particles_bulletimpacts.integer)
824 if (cl_particles_quake.integer)
826 if (cl_particles_smoke.integer)
827 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
831 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
832 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
833 CL_NewParticle(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);
837 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
838 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
839 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);
841 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
843 if (cl_particles_bulletimpacts.integer)
845 if (cl_particles_quake.integer)
847 if (cl_particles_smoke.integer)
848 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
852 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
853 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
854 CL_NewParticle(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);
858 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
859 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
861 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
863 if (cl_particles_bulletimpacts.integer)
865 if (cl_particles_quake.integer)
867 if (cl_particles_smoke.integer)
868 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
872 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
873 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
874 CL_NewParticle(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);
878 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
879 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
880 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);
882 else if (effectnameindex == EFFECT_TE_BLOOD)
884 if (!cl_particles_blood.integer)
886 if (cl_particles_quake.integer)
887 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
890 static double bloodaccumulator = 0;
891 qboolean immediatebloodstain = true;
892 //CL_NewParticle(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);
893 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
894 for (;bloodaccumulator > 0;bloodaccumulator--)
896 part = CL_NewParticle(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);
897 if (immediatebloodstain && part)
899 immediatebloodstain = false;
900 CL_ImmediateBloodStain(part);
905 else if (effectnameindex == EFFECT_TE_SPARK)
906 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
907 else if (effectnameindex == EFFECT_TE_PLASMABURN)
909 // plasma scorch mark
910 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
911 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
912 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
914 else if (effectnameindex == EFFECT_TE_GUNSHOT)
916 if (cl_particles_bulletimpacts.integer)
918 if (cl_particles_quake.integer)
919 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
922 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
923 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
924 CL_NewParticle(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);
928 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
929 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
931 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
933 if (cl_particles_bulletimpacts.integer)
935 if (cl_particles_quake.integer)
936 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
939 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
940 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
941 CL_NewParticle(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);
945 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
946 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
947 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);
949 else if (effectnameindex == EFFECT_TE_EXPLOSION)
951 CL_ParticleExplosion(center);
952 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);
954 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
956 CL_ParticleExplosion(center);
957 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);
959 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
961 if (cl_particles_quake.integer)
964 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
967 CL_NewParticle(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);
969 CL_NewParticle(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);
973 CL_ParticleExplosion(center);
974 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);
976 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
977 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);
978 else if (effectnameindex == EFFECT_TE_FLAMEJET)
980 count *= cl_particles_quality.value;
982 CL_NewParticle(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);
984 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
986 float i, j, inc, vel;
989 inc = 8 / cl_particles_quality.value;
990 for (i = -128;i < 128;i += inc)
992 for (j = -128;j < 128;j += inc)
994 dir[0] = j + lhrandom(0, inc);
995 dir[1] = i + lhrandom(0, inc);
997 org[0] = center[0] + dir[0];
998 org[1] = center[1] + dir[1];
999 org[2] = center[2] + lhrandom(0, 64);
1000 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1001 CL_NewParticle(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);
1005 else if (effectnameindex == EFFECT_TE_TELEPORT)
1007 float i, j, k, inc, vel;
1010 if (cl_particles_quake.integer)
1011 inc = 4 / cl_particles_quality.value;
1013 inc = 8 / cl_particles_quality.value;
1014 for (i = -16;i < 16;i += inc)
1016 for (j = -16;j < 16;j += inc)
1018 for (k = -24;k < 32;k += inc)
1020 VectorSet(dir, i*8, j*8, k*8);
1021 VectorNormalize(dir);
1022 vel = lhrandom(50, 113);
1023 if (cl_particles_quake.integer)
1024 CL_NewParticle(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);
1026 CL_NewParticle(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);
1030 if (!cl_particles_quake.integer)
1031 CL_NewParticle(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);
1032 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);
1034 else if (effectnameindex == EFFECT_TE_TEI_G3)
1035 CL_NewParticle(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);
1036 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1038 if (cl_particles_smoke.integer)
1040 count *= 0.25f * cl_particles_quality.value;
1042 CL_NewParticle(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);
1045 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1047 CL_ParticleExplosion(center);
1048 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);
1050 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1053 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1054 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1055 if (cl_particles_smoke.integer)
1056 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1057 CL_NewParticle(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);
1058 if (cl_particles_sparks.integer)
1059 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1060 CL_NewParticle(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);
1061 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);
1063 else if (effectnameindex == EFFECT_EF_FLAME)
1065 count *= 300 * cl_particles_quality.value;
1067 CL_NewParticle(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);
1068 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);
1070 else if (effectnameindex == EFFECT_EF_STARDUST)
1072 count *= 200 * cl_particles_quality.value;
1074 CL_NewParticle(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);
1075 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);
1077 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1081 int smoke, blood, bubbles, r, color;
1083 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1086 Vector4Set(light, 0, 0, 0, 0);
1088 if (effectnameindex == EFFECT_TR_ROCKET)
1089 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1090 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1092 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1093 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1095 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1097 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1098 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1102 matrix4x4_t tempmatrix;
1103 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1104 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);
1105 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1109 if (!spawnparticles)
1112 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1115 VectorSubtract(originmaxs, originmins, dir);
1116 len = VectorNormalizeLength(dir);
1119 dec = -ent->persistent.trail_time;
1120 ent->persistent.trail_time += len;
1121 if (ent->persistent.trail_time < 0.01f)
1124 // if we skip out, leave it reset
1125 ent->persistent.trail_time = 0.0f;
1130 // advance into this frame to reach the first puff location
1131 VectorMA(originmins, dec, dir, pos);
1134 smoke = cl_particles.integer && cl_particles_smoke.integer;
1135 blood = cl_particles.integer && cl_particles_blood.integer;
1136 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1137 qd = 1.0f / cl_particles_quality.value;
1144 if (effectnameindex == EFFECT_TR_BLOOD)
1146 if (cl_particles_quake.integer)
1148 color = particlepalette[67 + (rand()&3)];
1149 CL_NewParticle(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);
1154 CL_NewParticle(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);
1157 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1159 if (cl_particles_quake.integer)
1162 color = particlepalette[67 + (rand()&3)];
1163 CL_NewParticle(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);
1168 CL_NewParticle(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);
1174 if (effectnameindex == EFFECT_TR_ROCKET)
1176 if (cl_particles_quake.integer)
1179 color = particlepalette[ramp3[r]];
1180 CL_NewParticle(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);
1184 CL_NewParticle(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);
1185 CL_NewParticle(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);
1188 else if (effectnameindex == EFFECT_TR_GRENADE)
1190 if (cl_particles_quake.integer)
1193 color = particlepalette[ramp3[r]];
1194 CL_NewParticle(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);
1198 CL_NewParticle(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);
1201 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1203 if (cl_particles_quake.integer)
1206 color = particlepalette[52 + (rand()&7)];
1207 CL_NewParticle(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);
1208 CL_NewParticle(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);
1210 else if (gamemode == GAME_GOODVSBAD2)
1213 CL_NewParticle(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);
1217 color = particlepalette[20 + (rand()&7)];
1218 CL_NewParticle(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);
1221 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1223 if (cl_particles_quake.integer)
1226 color = particlepalette[230 + (rand()&7)];
1227 CL_NewParticle(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);
1228 CL_NewParticle(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);
1232 color = particlepalette[226 + (rand()&7)];
1233 CL_NewParticle(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);
1236 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1238 if (cl_particles_quake.integer)
1240 color = particlepalette[152 + (rand()&3)];
1241 CL_NewParticle(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);
1243 else if (gamemode == GAME_GOODVSBAD2)
1246 CL_NewParticle(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);
1248 else if (gamemode == GAME_PRYDON)
1251 CL_NewParticle(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);
1254 CL_NewParticle(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);
1256 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1259 CL_NewParticle(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);
1261 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1264 CL_NewParticle(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);
1266 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1267 CL_NewParticle(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);
1271 if (effectnameindex == EFFECT_TR_ROCKET)
1272 CL_NewParticle(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);
1273 else if (effectnameindex == EFFECT_TR_GRENADE)
1274 CL_NewParticle(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);
1276 // advance to next time and position
1279 VectorMA (pos, dec, dir, pos);
1282 ent->persistent.trail_time = len;
1284 else if (developer.integer >= 1)
1285 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1288 // this is also called on point effects with spawndlight = true and
1289 // spawnparticles = true
1290 // it is called CL_ParticleTrail because most code does not want to supply
1291 // these parameters, only trail handling does
1292 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)
1295 qboolean found = false;
1296 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1298 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1299 return; // no such effect
1301 VectorLerp(originmins, 0.5, originmaxs, center);
1302 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1304 int effectinfoindex;
1307 particleeffectinfo_t *info;
1309 vec3_t centervelocity;
1315 qboolean underwater;
1316 qboolean immediatebloodstain;
1318 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1319 VectorLerp(originmins, 0.5, originmaxs, center);
1320 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1321 supercontents = CL_PointSuperContents(center);
1322 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1323 VectorSubtract(originmaxs, originmins, traildir);
1324 traillen = VectorLength(traildir);
1325 VectorNormalize(traildir);
1326 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1328 if (info->effectnameindex == effectnameindex)
1331 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1333 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1336 // spawn a dlight if requested
1337 if (info->lightradiusstart > 0 && spawndlight)
1339 matrix4x4_t tempmatrix;
1340 if (info->trailspacing > 0)
1341 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1343 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1344 if (info->lighttime > 0 && info->lightradiusfade > 0)
1346 // light flash (explosion, etc)
1347 // called when effect starts
1348 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0], info->lightcolor[1], info->lightcolor[2], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1353 // called by CL_LinkNetworkEntity
1354 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1355 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1356 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1360 if (!spawnparticles)
1365 if (info->tex[1] > info->tex[0])
1367 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1368 tex = min(tex, info->tex[1] - 1);
1370 if(info->staintex[0] < 0)
1371 staintex = info->staintex[0];
1374 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1375 staintex = min(staintex, info->staintex[1] - 1);
1377 if (info->particletype == pt_decal)
1378 CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1]), tex, info->color[0], info->color[1]);
1379 else if (info->orientation == PARTICLE_HBEAM)
1380 CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1383 if (!cl_particles.integer)
1385 switch (info->particletype)
1387 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1388 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1389 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1390 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1391 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1392 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1395 VectorCopy(originmins, trailpos);
1396 if (info->trailspacing > 0)
1398 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1399 trailstep = info->trailspacing / cl_particles_quality.value;
1400 immediatebloodstain = false;
1404 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1406 immediatebloodstain = info->particletype == pt_blood || staintex;
1408 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1409 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1411 if (info->tex[1] > info->tex[0])
1413 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1414 tex = min(tex, info->tex[1] - 1);
1418 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1419 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1420 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1423 part = CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1424 if (immediatebloodstain && part)
1426 immediatebloodstain = false;
1427 CL_ImmediateBloodStain(part);
1430 VectorMA(trailpos, trailstep, traildir, trailpos);
1437 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1440 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)
1442 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1450 void CL_EntityParticles (const entity_t *ent)
1453 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1454 static vec3_t avelocities[NUMVERTEXNORMALS];
1455 if (!cl_particles.integer) return;
1456 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1458 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1460 if (!avelocities[0][0])
1461 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1462 avelocities[0][i] = lhrandom(0, 2.55);
1464 for (i = 0;i < NUMVERTEXNORMALS;i++)
1466 yaw = cl.time * avelocities[i][0];
1467 pitch = cl.time * avelocities[i][1];
1468 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1469 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1470 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1471 CL_NewParticle(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);
1476 void CL_ReadPointFile_f (void)
1478 vec3_t org, leakorg;
1480 char *pointfile = NULL, *pointfilepos, *t, tchar;
1481 char name[MAX_OSPATH];
1486 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1487 strlcat (name, ".pts", sizeof (name));
1488 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1491 Con_Printf("Could not open %s\n", name);
1495 Con_Printf("Reading %s...\n", name);
1496 VectorClear(leakorg);
1499 pointfilepos = pointfile;
1500 while (*pointfilepos)
1502 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1507 while (*t && *t != '\n' && *t != '\r')
1511 #if _MSC_VER >= 1400
1512 #define sscanf sscanf_s
1514 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1520 VectorCopy(org, leakorg);
1523 if (cl.num_particles < cl.max_particles - 3)
1526 CL_NewParticle(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);
1529 Mem_Free(pointfile);
1530 VectorCopy(leakorg, org);
1531 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1533 CL_NewParticle(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);
1534 CL_NewParticle(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);
1535 CL_NewParticle(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);
1540 CL_ParseParticleEffect
1542 Parse an effect out of the server message
1545 void CL_ParseParticleEffect (void)
1548 int i, count, msgcount, color;
1550 MSG_ReadVector(org, cls.protocol);
1551 for (i=0 ; i<3 ; i++)
1552 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1553 msgcount = MSG_ReadByte ();
1554 color = MSG_ReadByte ();
1556 if (msgcount == 255)
1561 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1566 CL_ParticleExplosion
1570 void CL_ParticleExplosion (const vec3_t org)
1576 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1577 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1579 if (cl_particles_quake.integer)
1581 for (i = 0;i < 1024;i++)
1587 color = particlepalette[ramp1[r]];
1588 CL_NewParticle(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);
1592 color = particlepalette[ramp2[r]];
1593 CL_NewParticle(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);
1599 i = CL_PointSuperContents(org);
1600 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1602 if (cl_particles.integer && cl_particles_bubbles.integer)
1603 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1604 CL_NewParticle(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);
1608 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1610 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1614 for (k = 0;k < 16;k++)
1617 VectorMA(org, 128, v2, v);
1618 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1619 if (trace.fraction >= 0.1)
1622 VectorSubtract(trace.endpos, org, v2);
1623 VectorScale(v2, 2.0f, v2);
1624 CL_NewParticle(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);
1630 if (cl_particles_explosions_shell.integer)
1631 R_NewExplosion(org);
1636 CL_ParticleExplosion2
1640 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1643 if (!cl_particles.integer) return;
1645 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1647 k = particlepalette[colorStart + (i % colorLength)];
1648 if (cl_particles_quake.integer)
1649 CL_NewParticle(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);
1651 CL_NewParticle(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);
1655 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1657 if (cl_particles_sparks.integer)
1659 sparkcount *= cl_particles_quality.value;
1660 while(sparkcount-- > 0)
1661 CL_NewParticle(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);
1665 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1667 if (cl_particles_smoke.integer)
1669 smokecount *= cl_particles_quality.value;
1670 while(smokecount-- > 0)
1671 CL_NewParticle(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);
1675 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)
1678 if (!cl_particles.integer) return;
1680 count = (int)(count * cl_particles_quality.value);
1683 k = particlepalette[colorbase + (rand()&3)];
1684 CL_NewParticle(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);
1688 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1691 float minz, maxz, lifetime = 30;
1692 if (!cl_particles.integer) return;
1693 if (dir[2] < 0) // falling
1695 minz = maxs[2] + dir[2] * 0.1;
1698 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1703 maxz = maxs[2] + dir[2] * 0.1;
1705 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1708 count = (int)(count * cl_particles_quality.value);
1713 if (!cl_particles_rain.integer) break;
1714 count *= 4; // ick, this should be in the mod or maps?
1718 k = particlepalette[colorbase + (rand()&3)];
1719 if (gamemode == GAME_GOODVSBAD2)
1720 CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1722 CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1726 if (!cl_particles_snow.integer) break;
1729 k = particlepalette[colorbase + (rand()&3)];
1730 if (gamemode == GAME_GOODVSBAD2)
1731 CL_NewParticle(pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1733 CL_NewParticle(pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1737 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1741 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1742 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1743 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1744 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1746 #define PARTICLETEXTURESIZE 64
1747 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1749 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1753 dz = 1 - (dx*dx+dy*dy);
1754 if (dz > 0) // it does hit the sphere
1758 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1759 VectorNormalize(normal);
1760 dot = DotProduct(normal, light);
1761 if (dot > 0.5) // interior reflection
1762 f += ((dot * 2) - 1);
1763 else if (dot < -0.5) // exterior reflection
1764 f += ((dot * -2) - 1);
1766 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1767 VectorNormalize(normal);
1768 dot = DotProduct(normal, light);
1769 if (dot > 0.5) // interior reflection
1770 f += ((dot * 2) - 1);
1771 else if (dot < -0.5) // exterior reflection
1772 f += ((dot * -2) - 1);
1774 f += 16; // just to give it a haze so you can see the outline
1775 f = bound(0, f, 255);
1776 return (unsigned char) f;
1782 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1783 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1785 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1786 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1787 *width = particlefontcellwidth;
1788 *height = particlefontcellheight;
1791 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1793 int basex, basey, w, h, y;
1794 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1795 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1796 Sys_Error("invalid particle texture size for autogenerating");
1797 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1798 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1801 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1804 float cx, cy, dx, dy, f, iradius;
1806 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1807 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1808 iradius = 1.0f / radius;
1809 alpha *= (1.0f / 255.0f);
1810 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1812 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1816 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1821 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1822 d[0] += (int)(f * (blue - d[0]));
1823 d[1] += (int)(f * (green - d[1]));
1824 d[2] += (int)(f * (red - d[2]));
1830 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1833 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1835 data[0] = bound(minb, data[0], maxb);
1836 data[1] = bound(ming, data[1], maxg);
1837 data[2] = bound(minr, data[2], maxr);
1841 void particletextureinvert(unsigned char *data)
1844 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1846 data[0] = 255 - data[0];
1847 data[1] = 255 - data[1];
1848 data[2] = 255 - data[2];
1852 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1853 static void R_InitBloodTextures (unsigned char *particletexturedata)
1856 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1859 for (i = 0;i < 8;i++)
1861 memset(&data[0][0][0], 255, sizeof(data));
1862 for (k = 0;k < 24;k++)
1863 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1864 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1865 particletextureinvert(&data[0][0][0]);
1866 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1870 for (i = 0;i < 8;i++)
1872 memset(&data[0][0][0], 255, sizeof(data));
1874 for (j = 1;j < 10;j++)
1875 for (k = min(j, m - 1);k < m;k++)
1876 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1877 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1878 particletextureinvert(&data[0][0][0]);
1879 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1884 //uncomment this to make engine save out particle font to a tga file when run
1885 //#define DUMPPARTICLEFONT
1887 static void R_InitParticleTexture (void)
1889 int x, y, d, i, k, m;
1890 int basex, basey, w, h;
1894 fs_offset_t filesize;
1896 // a note: decals need to modulate (multiply) the background color to
1897 // properly darken it (stain), and they need to be able to alpha fade,
1898 // this is a very difficult challenge because it means fading to white
1899 // (no change to background) rather than black (darkening everything
1900 // behind the whole decal polygon), and to accomplish this the texture is
1901 // inverted (dark red blood on white background becomes brilliant cyan
1902 // and white on black background) so we can alpha fade it to black, then
1903 // we invert it again during the blendfunc to make it work...
1905 #ifndef DUMPPARTICLEFONT
1906 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, false);
1909 particlefonttexture = decalskinframe->base;
1910 // TODO maybe allow custom grid size?
1911 particlefontwidth = image_width;
1912 particlefontheight = image_height;
1913 particlefontcellwidth = image_width / 8;
1914 particlefontcellheight = image_height / 8;
1915 particlefontcols = 8;
1916 particlefontrows = 8;
1921 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1922 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1924 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1925 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1926 particlefontcols = 8;
1927 particlefontrows = 8;
1929 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1932 for (i = 0;i < 8;i++)
1934 memset(&data[0][0][0], 255, sizeof(data));
1937 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1939 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1940 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1942 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1944 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1945 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1947 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1948 d = (noise2[y][x] - 128) * 3 + 192;
1950 d = (int)(d * (1-(dx*dx+dy*dy)));
1951 d = (d * noise1[y][x]) >> 7;
1952 d = bound(0, d, 255);
1953 data[y][x][3] = (unsigned char) d;
1960 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1964 memset(&data[0][0][0], 255, sizeof(data));
1965 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1967 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1968 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1970 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1971 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1972 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1975 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1978 memset(&data[0][0][0], 255, sizeof(data));
1979 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1981 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1982 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1984 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1985 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1986 d = bound(0, d, 255);
1987 data[y][x][3] = (unsigned char) d;
1990 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1993 memset(&data[0][0][0], 255, sizeof(data));
1994 light[0] = 1;light[1] = 1;light[2] = 1;
1995 VectorNormalize(light);
1996 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1998 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1999 // stretch upper half of bubble by +50% and shrink lower half by -50%
2000 // (this gives an elongated teardrop shape)
2002 dy = (dy - 0.5f) * 2.0f;
2004 dy = (dy - 0.5f) / 1.5f;
2005 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2007 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2008 // shrink bubble width to half
2010 data[y][x][3] = shadebubble(dx, dy, light);
2013 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
2016 memset(&data[0][0][0], 255, sizeof(data));
2017 light[0] = 1;light[1] = 1;light[2] = 1;
2018 VectorNormalize(light);
2019 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2021 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2022 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2024 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2025 data[y][x][3] = shadebubble(dx, dy, light);
2028 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
2030 // Blood particles and blood decals
2031 R_InitBloodTextures (particletexturedata);
2034 for (i = 0;i < 8;i++)
2036 memset(&data[0][0][0], 255, sizeof(data));
2037 for (k = 0;k < 12;k++)
2038 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2039 for (k = 0;k < 3;k++)
2040 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2041 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
2042 particletextureinvert(&data[0][0][0]);
2043 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
2046 #ifdef DUMPPARTICLEFONT
2047 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2050 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2051 particlefonttexture = decalskinframe->base;
2053 Mem_Free(particletexturedata);
2055 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2057 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2058 particletexture[i].texture = particlefonttexture;
2059 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2060 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2061 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2062 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2065 #ifndef DUMPPARTICLEFONT
2066 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
2067 if (!particletexture[tex_beam].texture)
2070 unsigned char noise3[64][64], data2[64][16][4];
2072 fractalnoise(&noise3[0][0], 64, 4);
2074 for (y = 0;y < 64;y++)
2076 dy = (y - 0.5f*64) / (64*0.5f-1);
2077 for (x = 0;x < 16;x++)
2079 dx = (x - 0.5f*16) / (16*0.5f-2);
2080 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2081 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2082 data2[y][x][3] = 255;
2086 #ifdef DUMPPARTICLEFONT
2087 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2089 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
2091 particletexture[tex_beam].s1 = 0;
2092 particletexture[tex_beam].t1 = 0;
2093 particletexture[tex_beam].s2 = 1;
2094 particletexture[tex_beam].t2 = 1;
2096 // now load an texcoord/texture override file
2097 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2104 if(!COM_ParseToken_Simple(&bufptr, true, false))
2106 if(!strcmp(com_token, "\n"))
2107 continue; // empty line
2108 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2109 particletexture[i].texture = particlefonttexture;
2111 if (!COM_ParseToken_Simple(&bufptr, true, false))
2113 if (!strcmp(com_token, "\n"))
2115 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2118 particletexture[i].s1 = atof(com_token);
2120 if (!COM_ParseToken_Simple(&bufptr, true, false))
2122 if (!strcmp(com_token, "\n"))
2124 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2127 particletexture[i].t1 = atof(com_token);
2129 if (!COM_ParseToken_Simple(&bufptr, true, false))
2131 if (!strcmp(com_token, "\n"))
2133 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2136 particletexture[i].s2 = atof(com_token);
2138 if (!COM_ParseToken_Simple(&bufptr, true, false))
2140 if (!strcmp(com_token, "\n"))
2142 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2145 particletexture[i].t2 = atof(com_token);
2151 static void r_part_start(void)
2154 // generate particlepalette for convenience from the main one
2155 for (i = 0;i < 256;i++)
2156 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2157 particletexturepool = R_AllocTexturePool();
2158 R_InitParticleTexture ();
2159 CL_Particles_LoadEffectInfo();
2162 static void r_part_shutdown(void)
2164 R_FreeTexturePool(&particletexturepool);
2167 static void r_part_newmap(void)
2170 R_SkinFrame_MarkUsed(decalskinframe);
2171 CL_Particles_LoadEffectInfo();
2174 #define BATCHSIZE 256
2175 unsigned short particle_elements[BATCHSIZE*6];
2177 void R_Particles_Init (void)
2180 for (i = 0;i < BATCHSIZE;i++)
2182 particle_elements[i*6+0] = i*4+0;
2183 particle_elements[i*6+1] = i*4+1;
2184 particle_elements[i*6+2] = i*4+2;
2185 particle_elements[i*6+3] = i*4+0;
2186 particle_elements[i*6+4] = i*4+2;
2187 particle_elements[i*6+5] = i*4+3;
2190 Cvar_RegisterVariable(&r_drawparticles);
2191 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2192 Cvar_RegisterVariable(&r_drawdecals);
2193 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2194 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2197 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2199 int surfacelistindex;
2201 float *v3f, *t2f, *c4f;
2202 particletexture_t *tex;
2203 float right[3], up[3], size, ca;
2204 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
2205 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2207 RSurf_ActiveWorldEntity();
2209 r_refdef.stats.decals += numsurfaces;
2210 R_Mesh_ResetTextureState();
2211 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2212 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2213 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2214 R_SetupGenericShader(true);
2215 GL_DepthMask(false);
2216 GL_DepthRange(0, 1);
2217 GL_PolygonOffset(0, 0);
2219 GL_CullFace(GL_NONE);
2221 // generate all the vertices at once
2222 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2224 d = cl.decals + surfacelist[surfacelistindex];
2227 c4f = particle_color4f + 16*surfacelistindex;
2228 ca = d->alpha * alphascale;
2229 if (r_refdef.fogenabled)
2230 ca *= RSurf_FogVertex(d->org);
2231 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2232 Vector4Copy(c4f, c4f + 4);
2233 Vector4Copy(c4f, c4f + 8);
2234 Vector4Copy(c4f, c4f + 12);
2236 // calculate vertex positions
2237 size = d->size * cl_particles_size.value;
2238 VectorVectors(d->normal, right, up);
2239 VectorScale(right, size, right);
2240 VectorScale(up, size, up);
2241 v3f = particle_vertex3f + 12*surfacelistindex;
2242 v3f[ 0] = d->org[0] - right[0] - up[0];
2243 v3f[ 1] = d->org[1] - right[1] - up[1];
2244 v3f[ 2] = d->org[2] - right[2] - up[2];
2245 v3f[ 3] = d->org[0] - right[0] + up[0];
2246 v3f[ 4] = d->org[1] - right[1] + up[1];
2247 v3f[ 5] = d->org[2] - right[2] + up[2];
2248 v3f[ 6] = d->org[0] + right[0] + up[0];
2249 v3f[ 7] = d->org[1] + right[1] + up[1];
2250 v3f[ 8] = d->org[2] + right[2] + up[2];
2251 v3f[ 9] = d->org[0] + right[0] - up[0];
2252 v3f[10] = d->org[1] + right[1] - up[1];
2253 v3f[11] = d->org[2] + right[2] - up[2];
2255 // calculate texcoords
2256 tex = &particletexture[d->texnum];
2257 t2f = particle_texcoord2f + 8*surfacelistindex;
2258 t2f[0] = tex->s1;t2f[1] = tex->t2;
2259 t2f[2] = tex->s1;t2f[3] = tex->t1;
2260 t2f[4] = tex->s2;t2f[5] = tex->t1;
2261 t2f[6] = tex->s2;t2f[7] = tex->t2;
2264 // now render the decals all at once
2265 // (this assumes they all use one particle font texture!)
2266 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2267 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2268 GL_LockArrays(0, numsurfaces*4);
2269 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2270 GL_LockArrays(0, 0);
2273 void R_DrawDecals (void)
2276 int drawdecals = r_drawdecals.integer;
2282 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2283 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2285 // LordHavoc: early out conditions
2289 decalfade = frametime * 256 / cl_decals_fadetime.value;
2290 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2291 drawdist2 = drawdist2*drawdist2;
2293 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2295 if (!decal->typeindex)
2298 if (cl.time > decal->time2 + cl_decals_time.value)
2300 decal->alpha -= decalfade;
2301 if (decal->alpha <= 0)
2307 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2309 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2310 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2316 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2322 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))
2323 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2326 decal->typeindex = 0;
2327 if (cl.free_decal > i)
2331 // reduce cl.num_decals if possible
2332 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2335 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2337 decal_t *olddecals = cl.decals;
2338 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2339 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2340 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2341 Mem_Free(olddecals);
2345 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2347 int surfacelistindex;
2348 int batchstart, batchcount;
2349 const particle_t *p;
2351 rtexture_t *texture;
2352 float *v3f, *t2f, *c4f;
2353 particletexture_t *tex;
2354 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2355 float ambient[3], diffuse[3], diffusenormal[3];
2356 vec4_t colormultiplier;
2357 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2359 RSurf_ActiveWorldEntity();
2361 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));
2363 r_refdef.stats.particles += numsurfaces;
2364 R_Mesh_ResetTextureState();
2365 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2366 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2367 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2368 R_SetupGenericShader(true);
2369 GL_DepthMask(false);
2370 GL_DepthRange(0, 1);
2371 GL_PolygonOffset(0, 0);
2373 GL_CullFace(GL_NONE);
2375 // first generate all the vertices at once
2376 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2378 p = cl.particles + surfacelist[surfacelistindex];
2380 blendmode = p->blendmode;
2382 c4f[0] = p->color[0] * colormultiplier[0];
2383 c4f[1] = p->color[1] * colormultiplier[1];
2384 c4f[2] = p->color[2] * colormultiplier[2];
2385 c4f[3] = p->alpha * colormultiplier[3];
2388 case PBLEND_INVALID:
2391 // additive and modulate can just fade out in fog (this is correct)
2392 if (r_refdef.fogenabled)
2393 c4f[3] *= RSurf_FogVertex(p->org);
2394 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2401 // note: lighting is not cheap!
2402 if (particletype[p->typeindex].lighting)
2404 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2405 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2406 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2407 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2409 // mix in the fog color
2410 if (r_refdef.fogenabled)
2412 fog = RSurf_FogVertex(p->org);
2414 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2415 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2416 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2420 // copy the color into the other three vertices
2421 Vector4Copy(c4f, c4f + 4);
2422 Vector4Copy(c4f, c4f + 8);
2423 Vector4Copy(c4f, c4f + 12);
2425 size = p->size * cl_particles_size.value;
2426 tex = &particletexture[p->texnum];
2427 switch(p->orientation)
2429 case PARTICLE_INVALID:
2430 case PARTICLE_BILLBOARD:
2431 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2432 VectorScale(r_refdef.view.up, size, up);
2433 v3f[ 0] = p->org[0] - right[0] - up[0];
2434 v3f[ 1] = p->org[1] - right[1] - up[1];
2435 v3f[ 2] = p->org[2] - right[2] - up[2];
2436 v3f[ 3] = p->org[0] - right[0] + up[0];
2437 v3f[ 4] = p->org[1] - right[1] + up[1];
2438 v3f[ 5] = p->org[2] - right[2] + up[2];
2439 v3f[ 6] = p->org[0] + right[0] + up[0];
2440 v3f[ 7] = p->org[1] + right[1] + up[1];
2441 v3f[ 8] = p->org[2] + right[2] + up[2];
2442 v3f[ 9] = p->org[0] + right[0] - up[0];
2443 v3f[10] = p->org[1] + right[1] - up[1];
2444 v3f[11] = p->org[2] + right[2] - up[2];
2445 t2f[0] = tex->s1;t2f[1] = tex->t2;
2446 t2f[2] = tex->s1;t2f[3] = tex->t1;
2447 t2f[4] = tex->s2;t2f[5] = tex->t1;
2448 t2f[6] = tex->s2;t2f[7] = tex->t2;
2450 case PARTICLE_ORIENTED_DOUBLESIDED:
2451 VectorVectors(p->vel, right, up);
2452 VectorScale(right, size * p->stretch, right);
2453 VectorScale(up, size, up);
2454 v3f[ 0] = p->org[0] - right[0] - up[0];
2455 v3f[ 1] = p->org[1] - right[1] - up[1];
2456 v3f[ 2] = p->org[2] - right[2] - up[2];
2457 v3f[ 3] = p->org[0] - right[0] + up[0];
2458 v3f[ 4] = p->org[1] - right[1] + up[1];
2459 v3f[ 5] = p->org[2] - right[2] + up[2];
2460 v3f[ 6] = p->org[0] + right[0] + up[0];
2461 v3f[ 7] = p->org[1] + right[1] + up[1];
2462 v3f[ 8] = p->org[2] + right[2] + up[2];
2463 v3f[ 9] = p->org[0] + right[0] - up[0];
2464 v3f[10] = p->org[1] + right[1] - up[1];
2465 v3f[11] = p->org[2] + right[2] - up[2];
2466 t2f[0] = tex->s1;t2f[1] = tex->t2;
2467 t2f[2] = tex->s1;t2f[3] = tex->t1;
2468 t2f[4] = tex->s2;t2f[5] = tex->t1;
2469 t2f[6] = tex->s2;t2f[7] = tex->t2;
2471 case PARTICLE_SPARK:
2472 len = VectorLength(p->vel);
2473 VectorNormalize2(p->vel, up);
2474 lenfactor = p->stretch * 0.04 * len;
2475 if(lenfactor < size * 0.5)
2476 lenfactor = size * 0.5;
2477 VectorMA(p->org, -lenfactor, up, v);
2478 VectorMA(p->org, lenfactor, up, up2);
2479 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2480 t2f[0] = tex->s1;t2f[1] = tex->t2;
2481 t2f[2] = tex->s1;t2f[3] = tex->t1;
2482 t2f[4] = tex->s2;t2f[5] = tex->t1;
2483 t2f[6] = tex->s2;t2f[7] = tex->t2;
2485 case PARTICLE_VBEAM:
2486 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2487 VectorSubtract(p->vel, p->org, up);
2488 VectorNormalize(up);
2489 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2490 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2491 t2f[0] = tex->s2;t2f[1] = v[0];
2492 t2f[2] = tex->s1;t2f[3] = v[0];
2493 t2f[4] = tex->s1;t2f[5] = v[1];
2494 t2f[6] = tex->s2;t2f[7] = v[1];
2496 case PARTICLE_HBEAM:
2497 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2498 VectorSubtract(p->vel, p->org, up);
2499 VectorNormalize(up);
2500 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2501 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2502 t2f[0] = v[0];t2f[1] = tex->t1;
2503 t2f[2] = v[0];t2f[3] = tex->t2;
2504 t2f[4] = v[1];t2f[5] = tex->t2;
2505 t2f[6] = v[1];t2f[7] = tex->t1;
2510 // now render batches of particles based on blendmode and texture
2511 blendmode = PBLEND_INVALID;
2513 GL_LockArrays(0, numsurfaces*4);
2516 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2518 p = cl.particles + surfacelist[surfacelistindex];
2520 if (blendmode != p->blendmode)
2522 blendmode = p->blendmode;
2526 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2528 case PBLEND_INVALID:
2530 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2533 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2537 if (texture != particletexture[p->texnum].texture)
2539 texture = particletexture[p->texnum].texture;
2540 R_Mesh_TexBind(0, R_GetTexture(texture));
2543 // iterate until we find a change in settings
2544 batchstart = surfacelistindex++;
2545 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2547 p = cl.particles + surfacelist[surfacelistindex];
2548 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2552 batchcount = surfacelistindex - batchstart;
2553 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2555 GL_LockArrays(0, 0);
2558 void R_DrawParticles (void)
2561 int drawparticles = r_drawparticles.integer;
2562 float minparticledist;
2564 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2570 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2571 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2573 // LordHavoc: early out conditions
2574 if (!cl.num_particles)
2577 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2578 gravity = frametime * cl.movevars_gravity;
2579 dvel = 1+4*frametime;
2580 decalfade = frametime * 255 / cl_decals_fadetime.value;
2581 update = frametime > 0;
2582 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2583 drawdist2 = drawdist2*drawdist2;
2585 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2589 if (cl.free_particle > i)
2590 cl.free_particle = i;
2596 if (p->delayedspawn > cl.time)
2598 p->delayedspawn = 0;
2602 p->size += p->sizeincrease * frametime;
2603 p->alpha -= p->alphafade * frametime;
2605 if (p->alpha <= 0 || p->die <= cl.time)
2608 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2610 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2612 if (p->typeindex == pt_blood)
2613 p->size += frametime * 8;
2615 p->vel[2] -= p->gravity * gravity;
2616 f = 1.0f - min(p->liquidfriction * frametime, 1);
2617 VectorScale(p->vel, f, p->vel);
2621 p->vel[2] -= p->gravity * gravity;
2624 f = 1.0f - min(p->airfriction * frametime, 1);
2625 VectorScale(p->vel, f, p->vel);
2629 VectorCopy(p->org, oldorg);
2630 VectorMA(p->org, frametime, p->vel, p->org);
2631 if (p->bounce && cl.time >= p->delayedcollisions)
2633 trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
2634 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2635 // or if the trace hit something flagged as NOIMPACT
2636 // then remove the particle
2637 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2639 VectorCopy(trace.endpos, p->org);
2640 // react if the particle hit something
2641 if (trace.fraction < 1)
2643 VectorCopy(trace.endpos, p->org);
2645 if (p->staintexnum >= 0)
2647 // blood - splash on solid
2648 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2651 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2652 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2653 if (cl_decals.integer)
2655 // create a decal for the blood splat
2656 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, 0xFFFFFF ^ p->staincolor, 0xFFFFFF ^ p->staincolor, p->staintexnum, p->size * 2, p->alpha); // staincolor needs to be inverted for decals!
2661 if (p->typeindex == pt_blood)
2663 // blood - splash on solid
2664 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2666 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2668 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)));
2669 if (cl_decals.integer)
2671 // create a decal for the blood splat
2672 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, 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 * 2, p->alpha);
2677 else if (p->bounce < 0)
2679 // bounce -1 means remove on impact
2684 // anything else - bounce off solid
2685 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2686 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2687 if (DotProduct(p->vel, p->vel) < 0.03)
2688 VectorClear(p->vel);
2694 if (p->typeindex != pt_static)
2696 switch (p->typeindex)
2698 case pt_entityparticle:
2699 // particle that removes itself after one rendered frame
2706 a = CL_PointSuperContents(p->org);
2707 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2711 a = CL_PointSuperContents(p->org);
2712 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2716 a = CL_PointSuperContents(p->org);
2717 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2721 if (cl.time > p->time2)
2724 p->time2 = cl.time + (rand() & 3) * 0.1;
2725 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2726 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2728 a = CL_PointSuperContents(p->org);
2729 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2737 else if (p->delayedspawn)
2741 // don't render particles too close to the view (they chew fillrate)
2742 // also don't render particles behind the view (useless)
2743 // further checks to cull to the frustum would be too slow here
2744 switch(p->typeindex)
2747 // beams have no culling
2748 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2751 if(cl_particles_visculling.integer)
2752 if (!r_refdef.viewcache.world_novis)
2753 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2755 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2757 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2760 // anything else just has to be in front of the viewer and visible at this distance
2761 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2762 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2769 if (cl.free_particle > i)
2770 cl.free_particle = i;
2773 // reduce cl.num_particles if possible
2774 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2777 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2779 particle_t *oldparticles = cl.particles;
2780 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2781 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2782 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2783 Mem_Free(oldparticles);