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"};
221 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
224 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
230 particleeffectinfo_t *info = NULL;
231 const char *text = textstart;
233 effectinfoindex = -1;
234 for (linenumber = 1;;linenumber++)
237 for (arrayindex = 0;arrayindex < 16;arrayindex++)
238 argv[arrayindex][0] = 0;
241 if (!COM_ParseToken_Simple(&text, true, false))
243 if (!strcmp(com_token, "\n"))
247 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
253 #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;}
254 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
255 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
256 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
257 #define readfloat(var) checkparms(2);var = atof(argv[1])
258 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
259 if (!strcmp(argv[0], "effect"))
264 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
266 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
269 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
271 if (particleeffectname[effectnameindex][0])
273 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
278 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
282 // if we run out of names, abort
283 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
285 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
288 info = particleeffectinfo + effectinfoindex;
289 info->effectnameindex = effectnameindex;
290 info->particletype = pt_alphastatic;
291 info->blendmode = particletype[info->particletype].blendmode;
292 info->orientation = particletype[info->particletype].orientation;
293 info->tex[0] = tex_particle;
294 info->tex[1] = tex_particle;
295 info->color[0] = 0xFFFFFF;
296 info->color[1] = 0xFFFFFF;
300 info->alpha[1] = 256;
301 info->alpha[2] = 256;
302 info->time[0] = 9999;
303 info->time[1] = 9999;
304 VectorSet(info->lightcolor, 1, 1, 1);
305 info->lightshadow = true;
306 info->lighttime = 9999;
307 info->stretchfactor = 1;
308 info->staincolor[0] = (unsigned int)-1;
309 info->staincolor[1] = (unsigned int)-1;
310 info->staintex[0] = -1;
311 info->staintex[1] = -1;
313 else if (info == NULL)
315 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
318 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
319 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
320 else if (!strcmp(argv[0], "type"))
323 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
324 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
325 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
326 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
327 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
328 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
329 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
330 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
331 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
332 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
333 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
334 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
335 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
336 info->blendmode = particletype[info->particletype].blendmode;
337 info->orientation = particletype[info->particletype].orientation;
339 else if (!strcmp(argv[0], "blend"))
342 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
343 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
344 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
345 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
347 else if (!strcmp(argv[0], "orientation"))
350 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
351 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
352 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
353 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
354 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
356 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
357 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
358 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
359 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
360 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
361 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
362 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
363 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
364 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
365 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
366 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
367 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
368 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
369 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
370 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
371 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
372 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
373 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
374 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
375 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
376 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
377 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
378 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
379 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
380 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
381 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
382 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
383 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1;}
385 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
394 int CL_ParticleEffectIndexForName(const char *name)
397 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
398 if (!strcmp(particleeffectname[i], name))
403 const char *CL_ParticleEffectNameForIndex(int i)
405 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
407 return particleeffectname[i];
410 // MUST match effectnameindex_t in client.h
411 static const char *standardeffectnames[EFFECT_TOTAL] =
435 "TE_TEI_BIGEXPLOSION",
451 void CL_Particles_LoadEffectInfo(void)
454 unsigned char *filedata;
455 fs_offset_t filesize;
456 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
457 memset(particleeffectname, 0, sizeof(particleeffectname));
458 for (i = 0;i < EFFECT_TOTAL;i++)
459 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
460 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
463 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
473 void CL_ReadPointFile_f (void);
474 void CL_Particles_Init (void)
476 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)");
477 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
479 Cvar_RegisterVariable (&cl_particles);
480 Cvar_RegisterVariable (&cl_particles_quality);
481 Cvar_RegisterVariable (&cl_particles_alpha);
482 Cvar_RegisterVariable (&cl_particles_size);
483 Cvar_RegisterVariable (&cl_particles_quake);
484 Cvar_RegisterVariable (&cl_particles_blood);
485 Cvar_RegisterVariable (&cl_particles_blood_alpha);
486 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
487 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
488 Cvar_RegisterVariable (&cl_particles_explosions_shell);
489 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
490 Cvar_RegisterVariable (&cl_particles_rain);
491 Cvar_RegisterVariable (&cl_particles_snow);
492 Cvar_RegisterVariable (&cl_particles_smoke);
493 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
494 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
495 Cvar_RegisterVariable (&cl_particles_sparks);
496 Cvar_RegisterVariable (&cl_particles_bubbles);
497 Cvar_RegisterVariable (&cl_particles_visculling);
498 Cvar_RegisterVariable (&cl_decals);
499 Cvar_RegisterVariable (&cl_decals_visculling);
500 Cvar_RegisterVariable (&cl_decals_time);
501 Cvar_RegisterVariable (&cl_decals_fadetime);
502 Cvar_RegisterVariable (&cl_decals_newsystem);
503 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
504 Cvar_RegisterVariable (&cl_decals_models);
505 Cvar_RegisterVariable (&cl_decals_bias);
506 Cvar_RegisterVariable (&cl_decals_max);
509 void CL_Particles_Shutdown (void)
513 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
514 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
516 // list of all 26 parameters:
517 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
518 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
519 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
520 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
521 // palpha - opacity of particle as 0-255 (can be more than 255)
522 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
523 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
524 // pgravity - how much effect gravity has on the particle (0-1)
525 // 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
526 // px,py,pz - starting origin of particle
527 // pvx,pvy,pvz - starting velocity of particle
528 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
529 // blendmode - one of the PBLEND_ values
530 // orientation - one of the PARTICLE_ values
531 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
532 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
533 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)
538 if (!cl_particles.integer)
540 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
541 if (cl.free_particle >= cl.max_particles)
544 lifetime = palpha / min(1, palphafade);
545 part = &cl.particles[cl.free_particle++];
546 if (cl.num_particles < cl.free_particle)
547 cl.num_particles = cl.free_particle;
548 memset(part, 0, sizeof(*part));
549 part->typeindex = ptypeindex;
550 part->blendmode = blendmode;
551 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
553 particletexture_t *tex = &particletexture[ptex];
554 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
555 part->orientation = PARTICLE_VBEAM;
557 part->orientation = PARTICLE_HBEAM;
560 part->orientation = orientation;
561 l2 = (int)lhrandom(0.5, 256.5);
563 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
564 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
565 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
566 part->staintexnum = staintex;
567 if(staincolor1 >= 0 && staincolor2 >= 0)
569 l2 = (int)lhrandom(0.5, 256.5);
571 if(blendmode == PBLEND_INVMOD)
573 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
574 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
575 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
579 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
580 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
581 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
583 if(r > 0xFF) r = 0xFF;
584 if(g > 0xFF) g = 0xFF;
585 if(b > 0xFF) b = 0xFF;
589 r = part->color[0]; // -1 is shorthand for stain = particle color
593 part->staincolor = r * 65536 + g * 256 + b;
596 part->sizeincrease = psizeincrease;
597 part->alpha = palpha;
598 part->alphafade = palphafade;
599 part->gravity = pgravity;
600 part->bounce = pbounce;
601 part->stretch = stretch;
603 part->org[0] = px + originjitter * v[0];
604 part->org[1] = py + originjitter * v[1];
605 part->org[2] = pz + originjitter * v[2];
606 part->vel[0] = pvx + velocityjitter * v[0];
607 part->vel[1] = pvy + velocityjitter * v[1];
608 part->vel[2] = pvz + velocityjitter * v[2];
610 part->airfriction = pairfriction;
611 part->liquidfriction = pliquidfriction;
612 part->die = cl.time + lifetime;
613 part->delayedcollisions = 0;
614 part->qualityreduction = pqualityreduction;
615 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
616 if (part->typeindex == pt_rain)
620 float lifetime = part->die - cl.time;
623 // turn raindrop into simple spark and create delayedspawn splash effect
624 part->typeindex = pt_spark;
626 VectorMA(part->org, lifetime, part->vel, endvec);
627 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
628 part->die = cl.time + lifetime * trace.fraction;
629 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);
632 part2->delayedspawn = part->die;
633 part2->die += part->die - cl.time;
634 for (i = rand() & 7;i < 10;i++)
636 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);
639 part2->delayedspawn = part->die;
640 part2->die += part->die - cl.time;
645 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
647 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
650 VectorMA(part->org, lifetime, part->vel, endvec);
651 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
652 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
658 static void CL_ImmediateBloodStain(particle_t *part)
663 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
664 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
666 VectorCopy(part->vel, v);
668 staintex = part->staintexnum;
669 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);
672 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
673 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
675 VectorCopy(part->vel, v);
677 staintex = tex_blooddecal[rand()&7];
678 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);
682 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
686 entity_render_t *ent = &cl.entities[hitent].render;
687 unsigned char color[3];
688 if (!cl_decals.integer)
690 if (!ent->allowdecals)
693 l2 = (int)lhrandom(0.5, 256.5);
695 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
696 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
697 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
699 if (cl_decals_newsystem.integer)
701 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);
705 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
706 if (cl.free_decal >= cl.max_decals)
708 decal = &cl.decals[cl.free_decal++];
709 if (cl.num_decals < cl.free_decal)
710 cl.num_decals = cl.free_decal;
711 memset(decal, 0, sizeof(*decal));
712 decal->decalsequence = cl.decalsequence++;
713 decal->typeindex = pt_decal;
714 decal->texnum = texnum;
715 VectorMA(org, cl_decals_bias.value, normal, decal->org);
716 VectorCopy(normal, decal->normal);
718 decal->alpha = alpha;
719 decal->time2 = cl.time;
720 decal->color[0] = color[0];
721 decal->color[1] = color[1];
722 decal->color[2] = color[2];
723 decal->owner = hitent;
724 decal->clusterindex = -1000; // no vis culling unless we're sure
727 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
728 decal->ownermodel = cl.entities[decal->owner].render.model;
729 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
730 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
734 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
736 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
738 decal->clusterindex = leaf->clusterindex;
743 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
746 float bestfrac, bestorg[3], bestnormal[3];
748 int besthitent = 0, hitent;
751 for (i = 0;i < 32;i++)
754 VectorMA(org, maxdist, org2, org2);
755 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
756 // take the closest trace result that doesn't end up hitting a NOMARKS
757 // surface (sky for example)
758 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
760 bestfrac = trace.fraction;
762 VectorCopy(trace.endpos, bestorg);
763 VectorCopy(trace.plane.normal, bestnormal);
767 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
770 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
771 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
772 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)
775 matrix4x4_t tempmatrix;
777 VectorLerp(originmins, 0.5, originmaxs, center);
778 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
779 if (effectnameindex == EFFECT_SVC_PARTICLE)
781 if (cl_particles.integer)
783 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
785 CL_ParticleExplosion(center);
786 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
787 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
790 count *= cl_particles_quality.value;
791 for (;count > 0;count--)
793 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
794 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);
799 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
800 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
801 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
802 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
803 else if (effectnameindex == EFFECT_TE_SPIKE)
805 if (cl_particles_bulletimpacts.integer)
807 if (cl_particles_quake.integer)
809 if (cl_particles_smoke.integer)
810 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
814 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
815 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
816 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);
820 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
821 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
823 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
825 if (cl_particles_bulletimpacts.integer)
827 if (cl_particles_quake.integer)
829 if (cl_particles_smoke.integer)
830 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
834 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
835 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
836 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);
840 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
841 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
842 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);
844 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
846 if (cl_particles_bulletimpacts.integer)
848 if (cl_particles_quake.integer)
850 if (cl_particles_smoke.integer)
851 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
855 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
856 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
857 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);
861 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
862 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
864 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
866 if (cl_particles_bulletimpacts.integer)
868 if (cl_particles_quake.integer)
870 if (cl_particles_smoke.integer)
871 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
875 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
876 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
877 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);
881 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
882 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
883 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);
885 else if (effectnameindex == EFFECT_TE_BLOOD)
887 if (!cl_particles_blood.integer)
889 if (cl_particles_quake.integer)
890 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
893 static double bloodaccumulator = 0;
894 qboolean immediatebloodstain = true;
895 //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);
896 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
897 for (;bloodaccumulator > 0;bloodaccumulator--)
899 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);
900 if (immediatebloodstain && part)
902 immediatebloodstain = false;
903 CL_ImmediateBloodStain(part);
908 else if (effectnameindex == EFFECT_TE_SPARK)
909 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
910 else if (effectnameindex == EFFECT_TE_PLASMABURN)
912 // plasma scorch mark
913 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
914 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
915 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
917 else if (effectnameindex == EFFECT_TE_GUNSHOT)
919 if (cl_particles_bulletimpacts.integer)
921 if (cl_particles_quake.integer)
922 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
925 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
926 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
927 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);
931 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
932 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
934 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
936 if (cl_particles_bulletimpacts.integer)
938 if (cl_particles_quake.integer)
939 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
942 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
943 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
944 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);
948 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
949 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
950 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);
952 else if (effectnameindex == EFFECT_TE_EXPLOSION)
954 CL_ParticleExplosion(center);
955 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);
957 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
959 CL_ParticleExplosion(center);
960 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);
962 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
964 if (cl_particles_quake.integer)
967 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
970 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);
972 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);
976 CL_ParticleExplosion(center);
977 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);
979 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
980 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);
981 else if (effectnameindex == EFFECT_TE_FLAMEJET)
983 count *= cl_particles_quality.value;
985 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);
987 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
989 float i, j, inc, vel;
992 inc = 8 / cl_particles_quality.value;
993 for (i = -128;i < 128;i += inc)
995 for (j = -128;j < 128;j += inc)
997 dir[0] = j + lhrandom(0, inc);
998 dir[1] = i + lhrandom(0, inc);
1000 org[0] = center[0] + dir[0];
1001 org[1] = center[1] + dir[1];
1002 org[2] = center[2] + lhrandom(0, 64);
1003 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1004 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);
1008 else if (effectnameindex == EFFECT_TE_TELEPORT)
1010 float i, j, k, inc, vel;
1013 if (cl_particles_quake.integer)
1014 inc = 4 / cl_particles_quality.value;
1016 inc = 8 / cl_particles_quality.value;
1017 for (i = -16;i < 16;i += inc)
1019 for (j = -16;j < 16;j += inc)
1021 for (k = -24;k < 32;k += inc)
1023 VectorSet(dir, i*8, j*8, k*8);
1024 VectorNormalize(dir);
1025 vel = lhrandom(50, 113);
1026 if (cl_particles_quake.integer)
1027 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);
1029 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);
1033 if (!cl_particles_quake.integer)
1034 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);
1035 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);
1037 else if (effectnameindex == EFFECT_TE_TEI_G3)
1038 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);
1039 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1041 if (cl_particles_smoke.integer)
1043 count *= 0.25f * cl_particles_quality.value;
1045 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);
1048 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1050 CL_ParticleExplosion(center);
1051 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);
1053 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1056 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1057 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1058 if (cl_particles_smoke.integer)
1059 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1060 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);
1061 if (cl_particles_sparks.integer)
1062 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1063 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);
1064 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);
1066 else if (effectnameindex == EFFECT_EF_FLAME)
1068 count *= 300 * cl_particles_quality.value;
1070 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);
1071 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);
1073 else if (effectnameindex == EFFECT_EF_STARDUST)
1075 count *= 200 * cl_particles_quality.value;
1077 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);
1078 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);
1080 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1084 int smoke, blood, bubbles, r, color;
1086 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1089 Vector4Set(light, 0, 0, 0, 0);
1091 if (effectnameindex == EFFECT_TR_ROCKET)
1092 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1093 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1095 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1096 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1098 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1100 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1101 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1105 matrix4x4_t tempmatrix;
1106 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1107 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);
1108 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1112 if (!spawnparticles)
1115 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1118 VectorSubtract(originmaxs, originmins, dir);
1119 len = VectorNormalizeLength(dir);
1122 dec = -ent->persistent.trail_time;
1123 ent->persistent.trail_time += len;
1124 if (ent->persistent.trail_time < 0.01f)
1127 // if we skip out, leave it reset
1128 ent->persistent.trail_time = 0.0f;
1133 // advance into this frame to reach the first puff location
1134 VectorMA(originmins, dec, dir, pos);
1137 smoke = cl_particles.integer && cl_particles_smoke.integer;
1138 blood = cl_particles.integer && cl_particles_blood.integer;
1139 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1140 qd = 1.0f / cl_particles_quality.value;
1147 if (effectnameindex == EFFECT_TR_BLOOD)
1149 if (cl_particles_quake.integer)
1151 color = particlepalette[67 + (rand()&3)];
1152 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);
1157 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);
1160 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1162 if (cl_particles_quake.integer)
1165 color = particlepalette[67 + (rand()&3)];
1166 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);
1171 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);
1177 if (effectnameindex == EFFECT_TR_ROCKET)
1179 if (cl_particles_quake.integer)
1182 color = particlepalette[ramp3[r]];
1183 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);
1187 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);
1188 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);
1191 else if (effectnameindex == EFFECT_TR_GRENADE)
1193 if (cl_particles_quake.integer)
1196 color = particlepalette[ramp3[r]];
1197 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);
1201 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);
1204 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1206 if (cl_particles_quake.integer)
1209 color = particlepalette[52 + (rand()&7)];
1210 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);
1211 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);
1213 else if (gamemode == GAME_GOODVSBAD2)
1216 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);
1220 color = particlepalette[20 + (rand()&7)];
1221 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);
1224 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1226 if (cl_particles_quake.integer)
1229 color = particlepalette[230 + (rand()&7)];
1230 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);
1231 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);
1235 color = particlepalette[226 + (rand()&7)];
1236 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);
1239 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1241 if (cl_particles_quake.integer)
1243 color = particlepalette[152 + (rand()&3)];
1244 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);
1246 else if (gamemode == GAME_GOODVSBAD2)
1249 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);
1251 else if (gamemode == GAME_PRYDON)
1254 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);
1257 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);
1259 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1262 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);
1264 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1267 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);
1269 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1270 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);
1274 if (effectnameindex == EFFECT_TR_ROCKET)
1275 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 else if (effectnameindex == EFFECT_TR_GRENADE)
1277 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);
1279 // advance to next time and position
1282 VectorMA (pos, dec, dir, pos);
1285 ent->persistent.trail_time = len;
1287 else if (developer.integer >= 1)
1288 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1291 // this is also called on point effects with spawndlight = true and
1292 // spawnparticles = true
1293 // it is called CL_ParticleTrail because most code does not want to supply
1294 // these parameters, only trail handling does
1295 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)
1298 qboolean found = false;
1299 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1301 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1302 return; // no such effect
1304 VectorLerp(originmins, 0.5, originmaxs, center);
1305 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1307 int effectinfoindex;
1310 particleeffectinfo_t *info;
1312 vec3_t centervelocity;
1318 qboolean underwater;
1319 qboolean immediatebloodstain;
1321 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1322 VectorLerp(originmins, 0.5, originmaxs, center);
1323 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1324 supercontents = CL_PointSuperContents(center);
1325 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1326 VectorSubtract(originmaxs, originmins, traildir);
1327 traillen = VectorLength(traildir);
1328 VectorNormalize(traildir);
1329 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1331 if (info->effectnameindex == effectnameindex)
1334 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1336 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1339 // spawn a dlight if requested
1340 if (info->lightradiusstart > 0 && spawndlight)
1342 matrix4x4_t tempmatrix;
1343 if (info->trailspacing > 0)
1344 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1346 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1347 if (info->lighttime > 0 && info->lightradiusfade > 0)
1349 // light flash (explosion, etc)
1350 // called when effect starts
1351 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);
1356 // called by CL_LinkNetworkEntity
1357 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1358 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);
1359 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1363 if (!spawnparticles)
1368 if (info->tex[1] > info->tex[0])
1370 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1371 tex = min(tex, info->tex[1] - 1);
1373 if(info->staintex[0] < 0)
1374 staintex = info->staintex[0];
1377 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1378 staintex = min(staintex, info->staintex[1] - 1);
1380 if (info->particletype == pt_decal)
1381 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]);
1382 else if (info->orientation == PARTICLE_HBEAM)
1383 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);
1386 if (!cl_particles.integer)
1388 switch (info->particletype)
1390 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1391 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1392 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1393 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1394 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1395 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1398 VectorCopy(originmins, trailpos);
1399 if (info->trailspacing > 0)
1401 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1402 trailstep = info->trailspacing / cl_particles_quality.value;
1403 immediatebloodstain = false;
1407 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1409 immediatebloodstain = info->particletype == pt_blood || staintex;
1411 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1412 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1414 if (info->tex[1] > info->tex[0])
1416 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1417 tex = min(tex, info->tex[1] - 1);
1421 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1422 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1423 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1426 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);
1427 if (immediatebloodstain && part)
1429 immediatebloodstain = false;
1430 CL_ImmediateBloodStain(part);
1433 VectorMA(trailpos, trailstep, traildir, trailpos);
1440 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1443 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)
1445 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1453 void CL_EntityParticles (const entity_t *ent)
1456 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1457 static vec3_t avelocities[NUMVERTEXNORMALS];
1458 if (!cl_particles.integer) return;
1459 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1461 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1463 if (!avelocities[0][0])
1464 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1465 avelocities[0][i] = lhrandom(0, 2.55);
1467 for (i = 0;i < NUMVERTEXNORMALS;i++)
1469 yaw = cl.time * avelocities[i][0];
1470 pitch = cl.time * avelocities[i][1];
1471 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1472 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1473 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1474 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);
1479 void CL_ReadPointFile_f (void)
1481 vec3_t org, leakorg;
1483 char *pointfile = NULL, *pointfilepos, *t, tchar;
1484 char name[MAX_OSPATH];
1489 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1490 strlcat (name, ".pts", sizeof (name));
1491 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1494 Con_Printf("Could not open %s\n", name);
1498 Con_Printf("Reading %s...\n", name);
1499 VectorClear(leakorg);
1502 pointfilepos = pointfile;
1503 while (*pointfilepos)
1505 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1510 while (*t && *t != '\n' && *t != '\r')
1514 #if _MSC_VER >= 1400
1515 #define sscanf sscanf_s
1517 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1523 VectorCopy(org, leakorg);
1526 if (cl.num_particles < cl.max_particles - 3)
1529 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);
1532 Mem_Free(pointfile);
1533 VectorCopy(leakorg, org);
1534 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1536 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);
1537 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);
1538 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);
1543 CL_ParseParticleEffect
1545 Parse an effect out of the server message
1548 void CL_ParseParticleEffect (void)
1551 int i, count, msgcount, color;
1553 MSG_ReadVector(org, cls.protocol);
1554 for (i=0 ; i<3 ; i++)
1555 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1556 msgcount = MSG_ReadByte ();
1557 color = MSG_ReadByte ();
1559 if (msgcount == 255)
1564 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1569 CL_ParticleExplosion
1573 void CL_ParticleExplosion (const vec3_t org)
1579 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1580 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1582 if (cl_particles_quake.integer)
1584 for (i = 0;i < 1024;i++)
1590 color = particlepalette[ramp1[r]];
1591 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);
1595 color = particlepalette[ramp2[r]];
1596 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);
1602 i = CL_PointSuperContents(org);
1603 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1605 if (cl_particles.integer && cl_particles_bubbles.integer)
1606 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1607 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);
1611 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1613 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1617 for (k = 0;k < 16;k++)
1620 VectorMA(org, 128, v2, v);
1621 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1622 if (trace.fraction >= 0.1)
1625 VectorSubtract(trace.endpos, org, v2);
1626 VectorScale(v2, 2.0f, v2);
1627 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);
1633 if (cl_particles_explosions_shell.integer)
1634 R_NewExplosion(org);
1639 CL_ParticleExplosion2
1643 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1646 if (!cl_particles.integer) return;
1648 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1650 k = particlepalette[colorStart + (i % colorLength)];
1651 if (cl_particles_quake.integer)
1652 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);
1654 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);
1658 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1660 if (cl_particles_sparks.integer)
1662 sparkcount *= cl_particles_quality.value;
1663 while(sparkcount-- > 0)
1664 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);
1668 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1670 if (cl_particles_smoke.integer)
1672 smokecount *= cl_particles_quality.value;
1673 while(smokecount-- > 0)
1674 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);
1678 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)
1681 if (!cl_particles.integer) return;
1683 count = (int)(count * cl_particles_quality.value);
1686 k = particlepalette[colorbase + (rand()&3)];
1687 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);
1691 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1694 float minz, maxz, lifetime = 30;
1695 if (!cl_particles.integer) return;
1696 if (dir[2] < 0) // falling
1698 minz = maxs[2] + dir[2] * 0.1;
1701 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1706 maxz = maxs[2] + dir[2] * 0.1;
1708 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1711 count = (int)(count * cl_particles_quality.value);
1716 if (!cl_particles_rain.integer) break;
1717 count *= 4; // ick, this should be in the mod or maps?
1721 k = particlepalette[colorbase + (rand()&3)];
1722 if (gamemode == GAME_GOODVSBAD2)
1723 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);
1725 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);
1729 if (!cl_particles_snow.integer) break;
1732 k = particlepalette[colorbase + (rand()&3)];
1733 if (gamemode == GAME_GOODVSBAD2)
1734 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);
1736 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);
1740 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1744 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1745 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1746 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1747 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1749 #define PARTICLETEXTURESIZE 64
1750 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1752 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1756 dz = 1 - (dx*dx+dy*dy);
1757 if (dz > 0) // it does hit the sphere
1761 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1762 VectorNormalize(normal);
1763 dot = DotProduct(normal, light);
1764 if (dot > 0.5) // interior reflection
1765 f += ((dot * 2) - 1);
1766 else if (dot < -0.5) // exterior reflection
1767 f += ((dot * -2) - 1);
1769 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1770 VectorNormalize(normal);
1771 dot = DotProduct(normal, light);
1772 if (dot > 0.5) // interior reflection
1773 f += ((dot * 2) - 1);
1774 else if (dot < -0.5) // exterior reflection
1775 f += ((dot * -2) - 1);
1777 f += 16; // just to give it a haze so you can see the outline
1778 f = bound(0, f, 255);
1779 return (unsigned char) f;
1785 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1786 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1788 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1789 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1790 *width = particlefontcellwidth;
1791 *height = particlefontcellheight;
1794 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1796 int basex, basey, w, h, y;
1797 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1798 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1799 Sys_Error("invalid particle texture size for autogenerating");
1800 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1801 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1804 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1807 float cx, cy, dx, dy, f, iradius;
1809 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1810 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1811 iradius = 1.0f / radius;
1812 alpha *= (1.0f / 255.0f);
1813 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1815 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1819 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1824 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1825 d[0] += (int)(f * (blue - d[0]));
1826 d[1] += (int)(f * (green - d[1]));
1827 d[2] += (int)(f * (red - d[2]));
1833 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1836 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1838 data[0] = bound(minb, data[0], maxb);
1839 data[1] = bound(ming, data[1], maxg);
1840 data[2] = bound(minr, data[2], maxr);
1844 void particletextureinvert(unsigned char *data)
1847 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1849 data[0] = 255 - data[0];
1850 data[1] = 255 - data[1];
1851 data[2] = 255 - data[2];
1855 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1856 static void R_InitBloodTextures (unsigned char *particletexturedata)
1859 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1862 for (i = 0;i < 8;i++)
1864 memset(&data[0][0][0], 255, sizeof(data));
1865 for (k = 0;k < 24;k++)
1866 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1867 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1868 particletextureinvert(&data[0][0][0]);
1869 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1873 for (i = 0;i < 8;i++)
1875 memset(&data[0][0][0], 255, sizeof(data));
1877 for (j = 1;j < 10;j++)
1878 for (k = min(j, m - 1);k < m;k++)
1879 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1880 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1881 particletextureinvert(&data[0][0][0]);
1882 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1887 //uncomment this to make engine save out particle font to a tga file when run
1888 //#define DUMPPARTICLEFONT
1890 static void R_InitParticleTexture (void)
1892 int x, y, d, i, k, m;
1893 int basex, basey, w, h;
1897 fs_offset_t filesize;
1899 // a note: decals need to modulate (multiply) the background color to
1900 // properly darken it (stain), and they need to be able to alpha fade,
1901 // this is a very difficult challenge because it means fading to white
1902 // (no change to background) rather than black (darkening everything
1903 // behind the whole decal polygon), and to accomplish this the texture is
1904 // inverted (dark red blood on white background becomes brilliant cyan
1905 // and white on black background) so we can alpha fade it to black, then
1906 // we invert it again during the blendfunc to make it work...
1908 #ifndef DUMPPARTICLEFONT
1909 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, false);
1912 particlefonttexture = decalskinframe->base;
1913 // TODO maybe allow custom grid size?
1914 particlefontwidth = image_width;
1915 particlefontheight = image_height;
1916 particlefontcellwidth = image_width / 8;
1917 particlefontcellheight = image_height / 8;
1918 particlefontcols = 8;
1919 particlefontrows = 8;
1924 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1925 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1927 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1928 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1929 particlefontcols = 8;
1930 particlefontrows = 8;
1932 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1935 for (i = 0;i < 8;i++)
1937 memset(&data[0][0][0], 255, sizeof(data));
1940 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1942 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1943 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1945 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1947 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1948 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1950 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1951 d = (noise2[y][x] - 128) * 3 + 192;
1953 d = (int)(d * (1-(dx*dx+dy*dy)));
1954 d = (d * noise1[y][x]) >> 7;
1955 d = bound(0, d, 255);
1956 data[y][x][3] = (unsigned char) d;
1963 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1967 memset(&data[0][0][0], 255, sizeof(data));
1968 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1970 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1971 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1973 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1974 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1975 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1978 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1981 memset(&data[0][0][0], 255, sizeof(data));
1982 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1984 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1985 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1987 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1988 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1989 d = bound(0, d, 255);
1990 data[y][x][3] = (unsigned char) d;
1993 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1996 memset(&data[0][0][0], 255, sizeof(data));
1997 light[0] = 1;light[1] = 1;light[2] = 1;
1998 VectorNormalize(light);
1999 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2001 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2002 // stretch upper half of bubble by +50% and shrink lower half by -50%
2003 // (this gives an elongated teardrop shape)
2005 dy = (dy - 0.5f) * 2.0f;
2007 dy = (dy - 0.5f) / 1.5f;
2008 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2010 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2011 // shrink bubble width to half
2013 data[y][x][3] = shadebubble(dx, dy, light);
2016 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
2019 memset(&data[0][0][0], 255, sizeof(data));
2020 light[0] = 1;light[1] = 1;light[2] = 1;
2021 VectorNormalize(light);
2022 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2024 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2025 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2027 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2028 data[y][x][3] = shadebubble(dx, dy, light);
2031 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
2033 // Blood particles and blood decals
2034 R_InitBloodTextures (particletexturedata);
2037 for (i = 0;i < 8;i++)
2039 memset(&data[0][0][0], 255, sizeof(data));
2040 for (k = 0;k < 12;k++)
2041 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2042 for (k = 0;k < 3;k++)
2043 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2044 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
2045 particletextureinvert(&data[0][0][0]);
2046 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
2049 #ifdef DUMPPARTICLEFONT
2050 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2053 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2054 particlefonttexture = decalskinframe->base;
2056 Mem_Free(particletexturedata);
2058 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2060 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2061 particletexture[i].texture = particlefonttexture;
2062 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2063 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2064 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2065 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2068 #ifndef DUMPPARTICLEFONT
2069 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
2070 if (!particletexture[tex_beam].texture)
2073 unsigned char noise3[64][64], data2[64][16][4];
2075 fractalnoise(&noise3[0][0], 64, 4);
2077 for (y = 0;y < 64;y++)
2079 dy = (y - 0.5f*64) / (64*0.5f-1);
2080 for (x = 0;x < 16;x++)
2082 dx = (x - 0.5f*16) / (16*0.5f-2);
2083 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2084 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2085 data2[y][x][3] = 255;
2089 #ifdef DUMPPARTICLEFONT
2090 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2092 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
2094 particletexture[tex_beam].s1 = 0;
2095 particletexture[tex_beam].t1 = 0;
2096 particletexture[tex_beam].s2 = 1;
2097 particletexture[tex_beam].t2 = 1;
2099 // now load an texcoord/texture override file
2100 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2107 if(!COM_ParseToken_Simple(&bufptr, true, false))
2109 if(!strcmp(com_token, "\n"))
2110 continue; // empty line
2111 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2112 particletexture[i].texture = particlefonttexture;
2114 if (!COM_ParseToken_Simple(&bufptr, true, false))
2116 if (!strcmp(com_token, "\n"))
2118 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2121 particletexture[i].s1 = atof(com_token);
2123 if (!COM_ParseToken_Simple(&bufptr, true, false))
2125 if (!strcmp(com_token, "\n"))
2127 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2130 particletexture[i].t1 = atof(com_token);
2132 if (!COM_ParseToken_Simple(&bufptr, true, false))
2134 if (!strcmp(com_token, "\n"))
2136 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2139 particletexture[i].s2 = atof(com_token);
2141 if (!COM_ParseToken_Simple(&bufptr, true, false))
2143 if (!strcmp(com_token, "\n"))
2145 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2148 particletexture[i].t2 = atof(com_token);
2154 static void r_part_start(void)
2157 // generate particlepalette for convenience from the main one
2158 for (i = 0;i < 256;i++)
2159 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2160 particletexturepool = R_AllocTexturePool();
2161 R_InitParticleTexture ();
2162 CL_Particles_LoadEffectInfo();
2165 static void r_part_shutdown(void)
2167 R_FreeTexturePool(&particletexturepool);
2170 static void r_part_newmap(void)
2173 R_SkinFrame_MarkUsed(decalskinframe);
2174 CL_Particles_LoadEffectInfo();
2177 #define BATCHSIZE 256
2178 unsigned short particle_elements[BATCHSIZE*6];
2180 void R_Particles_Init (void)
2183 for (i = 0;i < BATCHSIZE;i++)
2185 particle_elements[i*6+0] = i*4+0;
2186 particle_elements[i*6+1] = i*4+1;
2187 particle_elements[i*6+2] = i*4+2;
2188 particle_elements[i*6+3] = i*4+0;
2189 particle_elements[i*6+4] = i*4+2;
2190 particle_elements[i*6+5] = i*4+3;
2193 Cvar_RegisterVariable(&r_drawparticles);
2194 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2195 Cvar_RegisterVariable(&r_drawdecals);
2196 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2197 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2200 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2202 int surfacelistindex;
2204 float *v3f, *t2f, *c4f;
2205 particletexture_t *tex;
2206 float right[3], up[3], size, ca;
2207 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
2208 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2210 RSurf_ActiveWorldEntity();
2212 r_refdef.stats.drawndecals += numsurfaces;
2213 R_Mesh_ResetTextureState();
2214 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2215 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2216 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2217 R_SetupGenericShader(true);
2218 GL_DepthMask(false);
2219 GL_DepthRange(0, 1);
2220 GL_PolygonOffset(0, 0);
2222 GL_CullFace(GL_NONE);
2224 // generate all the vertices at once
2225 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2227 d = cl.decals + surfacelist[surfacelistindex];
2230 c4f = particle_color4f + 16*surfacelistindex;
2231 ca = d->alpha * alphascale;
2232 if (r_refdef.fogenabled)
2233 ca *= RSurf_FogVertex(d->org);
2234 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2235 Vector4Copy(c4f, c4f + 4);
2236 Vector4Copy(c4f, c4f + 8);
2237 Vector4Copy(c4f, c4f + 12);
2239 // calculate vertex positions
2240 size = d->size * cl_particles_size.value;
2241 VectorVectors(d->normal, right, up);
2242 VectorScale(right, size, right);
2243 VectorScale(up, size, up);
2244 v3f = particle_vertex3f + 12*surfacelistindex;
2245 v3f[ 0] = d->org[0] - right[0] - up[0];
2246 v3f[ 1] = d->org[1] - right[1] - up[1];
2247 v3f[ 2] = d->org[2] - right[2] - up[2];
2248 v3f[ 3] = d->org[0] - right[0] + up[0];
2249 v3f[ 4] = d->org[1] - right[1] + up[1];
2250 v3f[ 5] = d->org[2] - right[2] + up[2];
2251 v3f[ 6] = d->org[0] + right[0] + up[0];
2252 v3f[ 7] = d->org[1] + right[1] + up[1];
2253 v3f[ 8] = d->org[2] + right[2] + up[2];
2254 v3f[ 9] = d->org[0] + right[0] - up[0];
2255 v3f[10] = d->org[1] + right[1] - up[1];
2256 v3f[11] = d->org[2] + right[2] - up[2];
2258 // calculate texcoords
2259 tex = &particletexture[d->texnum];
2260 t2f = particle_texcoord2f + 8*surfacelistindex;
2261 t2f[0] = tex->s1;t2f[1] = tex->t2;
2262 t2f[2] = tex->s1;t2f[3] = tex->t1;
2263 t2f[4] = tex->s2;t2f[5] = tex->t1;
2264 t2f[6] = tex->s2;t2f[7] = tex->t2;
2267 // now render the decals all at once
2268 // (this assumes they all use one particle font texture!)
2269 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2270 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2271 GL_LockArrays(0, numsurfaces*4);
2272 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2273 GL_LockArrays(0, 0);
2276 void R_DrawDecals (void)
2279 int drawdecals = r_drawdecals.integer;
2284 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2286 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2287 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2289 // LordHavoc: early out conditions
2293 decalfade = frametime * 256 / cl_decals_fadetime.value;
2294 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2295 drawdist2 = drawdist2*drawdist2;
2297 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2299 if (!decal->typeindex)
2302 if (killsequence - decal->decalsequence > 0)
2305 if (cl.time > decal->time2 + cl_decals_time.value)
2307 decal->alpha -= decalfade;
2308 if (decal->alpha <= 0)
2314 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2316 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2317 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2323 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2329 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))
2330 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2333 decal->typeindex = 0;
2334 if (cl.free_decal > i)
2338 // reduce cl.num_decals if possible
2339 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2342 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2344 decal_t *olddecals = cl.decals;
2345 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2346 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2347 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2348 Mem_Free(olddecals);
2351 r_refdef.stats.totaldecals = cl.num_decals;
2354 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2356 int surfacelistindex;
2357 int batchstart, batchcount;
2358 const particle_t *p;
2360 rtexture_t *texture;
2361 float *v3f, *t2f, *c4f;
2362 particletexture_t *tex;
2363 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2364 float ambient[3], diffuse[3], diffusenormal[3];
2365 vec4_t colormultiplier;
2366 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2368 RSurf_ActiveWorldEntity();
2370 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));
2372 r_refdef.stats.particles += numsurfaces;
2373 R_Mesh_ResetTextureState();
2374 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2375 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2376 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2377 R_SetupGenericShader(true);
2378 GL_DepthMask(false);
2379 GL_DepthRange(0, 1);
2380 GL_PolygonOffset(0, 0);
2382 GL_CullFace(GL_NONE);
2384 // first generate all the vertices at once
2385 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2387 p = cl.particles + surfacelist[surfacelistindex];
2389 blendmode = p->blendmode;
2391 c4f[0] = p->color[0] * colormultiplier[0];
2392 c4f[1] = p->color[1] * colormultiplier[1];
2393 c4f[2] = p->color[2] * colormultiplier[2];
2394 c4f[3] = p->alpha * colormultiplier[3];
2397 case PBLEND_INVALID:
2400 // additive and modulate can just fade out in fog (this is correct)
2401 if (r_refdef.fogenabled)
2402 c4f[3] *= RSurf_FogVertex(p->org);
2403 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2410 // note: lighting is not cheap!
2411 if (particletype[p->typeindex].lighting)
2413 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2414 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2415 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2416 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2418 // mix in the fog color
2419 if (r_refdef.fogenabled)
2421 fog = RSurf_FogVertex(p->org);
2423 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2424 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2425 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2429 // copy the color into the other three vertices
2430 Vector4Copy(c4f, c4f + 4);
2431 Vector4Copy(c4f, c4f + 8);
2432 Vector4Copy(c4f, c4f + 12);
2434 size = p->size * cl_particles_size.value;
2435 tex = &particletexture[p->texnum];
2436 switch(p->orientation)
2438 case PARTICLE_INVALID:
2439 case PARTICLE_BILLBOARD:
2440 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2441 VectorScale(r_refdef.view.up, size, up);
2442 v3f[ 0] = p->org[0] - right[0] - up[0];
2443 v3f[ 1] = p->org[1] - right[1] - up[1];
2444 v3f[ 2] = p->org[2] - right[2] - up[2];
2445 v3f[ 3] = p->org[0] - right[0] + up[0];
2446 v3f[ 4] = p->org[1] - right[1] + up[1];
2447 v3f[ 5] = p->org[2] - right[2] + up[2];
2448 v3f[ 6] = p->org[0] + right[0] + up[0];
2449 v3f[ 7] = p->org[1] + right[1] + up[1];
2450 v3f[ 8] = p->org[2] + right[2] + up[2];
2451 v3f[ 9] = p->org[0] + right[0] - up[0];
2452 v3f[10] = p->org[1] + right[1] - up[1];
2453 v3f[11] = p->org[2] + right[2] - up[2];
2454 t2f[0] = tex->s1;t2f[1] = tex->t2;
2455 t2f[2] = tex->s1;t2f[3] = tex->t1;
2456 t2f[4] = tex->s2;t2f[5] = tex->t1;
2457 t2f[6] = tex->s2;t2f[7] = tex->t2;
2459 case PARTICLE_ORIENTED_DOUBLESIDED:
2460 VectorVectors(p->vel, right, up);
2461 VectorScale(right, size * p->stretch, right);
2462 VectorScale(up, size, up);
2463 v3f[ 0] = p->org[0] - right[0] - up[0];
2464 v3f[ 1] = p->org[1] - right[1] - up[1];
2465 v3f[ 2] = p->org[2] - right[2] - up[2];
2466 v3f[ 3] = p->org[0] - right[0] + up[0];
2467 v3f[ 4] = p->org[1] - right[1] + up[1];
2468 v3f[ 5] = p->org[2] - right[2] + up[2];
2469 v3f[ 6] = p->org[0] + right[0] + up[0];
2470 v3f[ 7] = p->org[1] + right[1] + up[1];
2471 v3f[ 8] = p->org[2] + right[2] + up[2];
2472 v3f[ 9] = p->org[0] + right[0] - up[0];
2473 v3f[10] = p->org[1] + right[1] - up[1];
2474 v3f[11] = p->org[2] + right[2] - up[2];
2475 t2f[0] = tex->s1;t2f[1] = tex->t2;
2476 t2f[2] = tex->s1;t2f[3] = tex->t1;
2477 t2f[4] = tex->s2;t2f[5] = tex->t1;
2478 t2f[6] = tex->s2;t2f[7] = tex->t2;
2480 case PARTICLE_SPARK:
2481 len = VectorLength(p->vel);
2482 VectorNormalize2(p->vel, up);
2483 lenfactor = p->stretch * 0.04 * len;
2484 if(lenfactor < size * 0.5)
2485 lenfactor = size * 0.5;
2486 VectorMA(p->org, -lenfactor, up, v);
2487 VectorMA(p->org, lenfactor, up, up2);
2488 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2489 t2f[0] = tex->s1;t2f[1] = tex->t2;
2490 t2f[2] = tex->s1;t2f[3] = tex->t1;
2491 t2f[4] = tex->s2;t2f[5] = tex->t1;
2492 t2f[6] = tex->s2;t2f[7] = tex->t2;
2494 case PARTICLE_VBEAM:
2495 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2496 VectorSubtract(p->vel, p->org, up);
2497 VectorNormalize(up);
2498 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2499 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2500 t2f[0] = tex->s2;t2f[1] = v[0];
2501 t2f[2] = tex->s1;t2f[3] = v[0];
2502 t2f[4] = tex->s1;t2f[5] = v[1];
2503 t2f[6] = tex->s2;t2f[7] = v[1];
2505 case PARTICLE_HBEAM:
2506 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2507 VectorSubtract(p->vel, p->org, up);
2508 VectorNormalize(up);
2509 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2510 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2511 t2f[0] = v[0];t2f[1] = tex->t1;
2512 t2f[2] = v[0];t2f[3] = tex->t2;
2513 t2f[4] = v[1];t2f[5] = tex->t2;
2514 t2f[6] = v[1];t2f[7] = tex->t1;
2519 // now render batches of particles based on blendmode and texture
2520 blendmode = PBLEND_INVALID;
2522 GL_LockArrays(0, numsurfaces*4);
2525 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2527 p = cl.particles + surfacelist[surfacelistindex];
2529 if (blendmode != p->blendmode)
2531 blendmode = p->blendmode;
2535 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2537 case PBLEND_INVALID:
2539 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2542 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2546 if (texture != particletexture[p->texnum].texture)
2548 texture = particletexture[p->texnum].texture;
2549 R_Mesh_TexBind(0, R_GetTexture(texture));
2552 // iterate until we find a change in settings
2553 batchstart = surfacelistindex++;
2554 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2556 p = cl.particles + surfacelist[surfacelistindex];
2557 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2561 batchcount = surfacelistindex - batchstart;
2562 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2564 GL_LockArrays(0, 0);
2567 void R_DrawParticles (void)
2570 int drawparticles = r_drawparticles.integer;
2571 float minparticledist;
2573 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2579 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2580 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2582 // LordHavoc: early out conditions
2583 if (!cl.num_particles)
2586 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2587 gravity = frametime * cl.movevars_gravity;
2588 dvel = 1+4*frametime;
2589 decalfade = frametime * 255 / cl_decals_fadetime.value;
2590 update = frametime > 0;
2591 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2592 drawdist2 = drawdist2*drawdist2;
2594 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2598 if (cl.free_particle > i)
2599 cl.free_particle = i;
2605 if (p->delayedspawn > cl.time)
2607 p->delayedspawn = 0;
2611 p->size += p->sizeincrease * frametime;
2612 p->alpha -= p->alphafade * frametime;
2614 if (p->alpha <= 0 || p->die <= cl.time)
2617 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2619 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2621 if (p->typeindex == pt_blood)
2622 p->size += frametime * 8;
2624 p->vel[2] -= p->gravity * gravity;
2625 f = 1.0f - min(p->liquidfriction * frametime, 1);
2626 VectorScale(p->vel, f, p->vel);
2630 p->vel[2] -= p->gravity * gravity;
2633 f = 1.0f - min(p->airfriction * frametime, 1);
2634 VectorScale(p->vel, f, p->vel);
2638 VectorCopy(p->org, oldorg);
2639 VectorMA(p->org, frametime, p->vel, p->org);
2640 if (p->bounce && cl.time >= p->delayedcollisions)
2642 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);
2643 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2644 // or if the trace hit something flagged as NOIMPACT
2645 // then remove the particle
2646 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2648 VectorCopy(trace.endpos, p->org);
2649 // react if the particle hit something
2650 if (trace.fraction < 1)
2652 VectorCopy(trace.endpos, p->org);
2654 if (p->staintexnum >= 0)
2656 // blood - splash on solid
2657 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2660 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2661 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2662 if (cl_decals.integer)
2664 // create a decal for the blood splat
2665 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!
2670 if (p->typeindex == pt_blood)
2672 // blood - splash on solid
2673 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2675 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2677 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)));
2678 if (cl_decals.integer)
2680 // create a decal for the blood splat
2681 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);
2686 else if (p->bounce < 0)
2688 // bounce -1 means remove on impact
2693 // anything else - bounce off solid
2694 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2695 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2696 if (DotProduct(p->vel, p->vel) < 0.03)
2697 VectorClear(p->vel);
2703 if (p->typeindex != pt_static)
2705 switch (p->typeindex)
2707 case pt_entityparticle:
2708 // particle that removes itself after one rendered frame
2715 a = CL_PointSuperContents(p->org);
2716 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2720 a = CL_PointSuperContents(p->org);
2721 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2725 a = CL_PointSuperContents(p->org);
2726 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2730 if (cl.time > p->time2)
2733 p->time2 = cl.time + (rand() & 3) * 0.1;
2734 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2735 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2737 a = CL_PointSuperContents(p->org);
2738 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2746 else if (p->delayedspawn)
2750 // don't render particles too close to the view (they chew fillrate)
2751 // also don't render particles behind the view (useless)
2752 // further checks to cull to the frustum would be too slow here
2753 switch(p->typeindex)
2756 // beams have no culling
2757 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2760 if(cl_particles_visculling.integer)
2761 if (!r_refdef.viewcache.world_novis)
2762 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2764 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2766 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2769 // anything else just has to be in front of the viewer and visible at this distance
2770 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2771 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2778 if (cl.free_particle > i)
2779 cl.free_particle = i;
2782 // reduce cl.num_particles if possible
2783 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2786 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2788 particle_t *oldparticles = cl.particles;
2789 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2790 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2791 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2792 Mem_Free(oldparticles);