2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
48 typedef struct particleeffectinfo_s
50 int effectnameindex; // which effect this belongs to
51 // PARTICLEEFFECT_* bits
53 // blood effects may spawn very few particles, so proper fraction-overflow
54 // handling is very important, this variable keeps track of the fraction
55 double particleaccumulator;
56 // the math is: countabsolute + requestedcount * countmultiplier * quality
57 // absolute number of particles to spawn, often used for decals
58 // (unaffected by quality and requestedcount)
60 // multiplier for the number of particles CL_ParticleEffect was told to
61 // spawn, most effects do not really have a count and hence use 1, so
62 // this is often the actual count to spawn, not merely a multiplier
63 float countmultiplier;
64 // if > 0 this causes the particle to spawn in an evenly spaced line from
65 // originmins to originmaxs (causing them to describe a trail, not a box)
67 // type of particle to spawn (defines some aspects of behavior)
69 // blending mode used on this particle type
71 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
72 porientation_t orientation;
73 // range of colors to choose from in hex RRGGBB (like HTML color tags),
74 // randomly interpolated at spawn
75 unsigned int color[2];
76 // a random texture is chosen in this range (note the second value is one
77 // past the last choosable, so for example 8,16 chooses any from 8 up and
79 // if start and end of the range are the same, no randomization is done
81 // range of size values randomly chosen when spawning, plus size increase over time
83 // range of alpha values randomly chosen when spawning, plus alpha fade
85 // how long the particle should live (note it is also removed if alpha drops to 0)
87 // how much gravity affects this particle (negative makes it fly up!)
89 // how much bounce the particle has when it hits a surface
90 // if negative the particle is removed on impact
92 // if in air this friction is applied
93 // if negative the particle accelerates
95 // if in liquid (water/slime/lava) this friction is applied
96 // if negative the particle accelerates
98 // these offsets are added to the values given to particleeffect(), and
99 // then an ellipsoid-shaped jitter is added as defined by these
100 // (they are the 3 radii)
102 // stretch velocity factor (used for sparks)
103 float originoffset[3];
104 float velocityoffset[3];
105 float originjitter[3];
106 float velocityjitter[3];
107 float velocitymultiplier;
108 // an effect can also spawn a dlight
109 float lightradiusstart;
110 float lightradiusfade;
113 qboolean lightshadow;
115 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
120 float rotate[4]; // min/max base angle, min/max rotation over time
122 particleeffectinfo_t;
124 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 int numparticleeffectinfo;
127 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
129 static int particlepalette[256];
131 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
132 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
133 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
134 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
135 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
136 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
137 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
138 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
139 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
140 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
141 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
142 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
143 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
144 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
145 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
146 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
147 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
148 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
149 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
150 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
151 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
152 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
153 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
154 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
155 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
156 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
157 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
158 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
159 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
160 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
161 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
162 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
165 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
166 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
167 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
169 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
171 // particletexture_t is a rectangle in the particlefonttexture
172 typedef struct particletexture_s
175 float s1, t1, s2, t2;
179 static rtexturepool_t *particletexturepool;
180 static rtexture_t *particlefonttexture;
181 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
182 skinframe_t *decalskinframe;
184 // texture numbers in particle font
185 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
186 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
187 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
188 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
189 static const int tex_rainsplash = 32;
190 static const int tex_particle = 63;
191 static const int tex_bubble = 62;
192 static const int tex_raindrop = 61;
193 static const int tex_beam = 60;
195 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
196 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
197 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
198 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
199 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
200 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
201 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
202 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
203 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
204 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
205 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
206 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
207 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
208 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
209 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
210 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
211 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
212 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
213 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
214 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
215 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
216 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
217 cvar_t cl_particles_collisions = {CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"};
218 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
219 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
220 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
221 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
222 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "0", "enables new advanced decal system"};
223 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)"};
224 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
225 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
226 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
229 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
234 particleeffectinfo_t *info = NULL;
235 const char *text = textstart;
237 for (linenumber = 1;;linenumber++)
240 for (arrayindex = 0;arrayindex < 16;arrayindex++)
241 argv[arrayindex][0] = 0;
244 if (!COM_ParseToken_Simple(&text, true, false))
246 if (!strcmp(com_token, "\n"))
250 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
256 #define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;}
257 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
258 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
259 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
260 #define readfloat(var) checkparms(2);var = atof(argv[1])
261 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
262 if (!strcmp(argv[0], "effect"))
266 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
268 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
271 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
273 if (particleeffectname[effectnameindex][0])
275 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
280 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
284 // if we run out of names, abort
285 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
287 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
290 info = particleeffectinfo + numparticleeffectinfo++;
291 info->effectnameindex = effectnameindex;
292 info->particletype = pt_alphastatic;
293 info->blendmode = particletype[info->particletype].blendmode;
294 info->orientation = particletype[info->particletype].orientation;
295 info->tex[0] = tex_particle;
296 info->tex[1] = tex_particle;
297 info->color[0] = 0xFFFFFF;
298 info->color[1] = 0xFFFFFF;
302 info->alpha[1] = 256;
303 info->alpha[2] = 256;
304 info->time[0] = 9999;
305 info->time[1] = 9999;
306 VectorSet(info->lightcolor, 1, 1, 1);
307 info->lightshadow = true;
308 info->lighttime = 9999;
309 info->stretchfactor = 1;
310 info->staincolor[0] = (unsigned int)-1;
311 info->staincolor[1] = (unsigned int)-1;
312 info->staintex[0] = -1;
313 info->staintex[1] = -1;
314 info->stainalpha[0] = 1;
315 info->stainalpha[1] = 1;
316 info->stainsize[0] = 2;
317 info->stainsize[1] = 2;
319 info->rotate[1] = 360;
323 else if (info == NULL)
325 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
328 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
329 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
330 else if (!strcmp(argv[0], "type"))
333 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
334 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
335 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
336 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
337 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
338 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
339 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
340 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
341 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
342 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
343 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
344 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
345 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
346 info->blendmode = particletype[info->particletype].blendmode;
347 info->orientation = particletype[info->particletype].orientation;
349 else if (!strcmp(argv[0], "blend"))
352 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
353 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
354 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
355 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
357 else if (!strcmp(argv[0], "orientation"))
360 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
361 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
362 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
363 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
364 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
366 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
367 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
368 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
369 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
370 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
371 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
372 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
373 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
374 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
375 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
376 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
377 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
378 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
379 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
380 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
381 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
382 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
383 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
384 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
385 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
386 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
387 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
388 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
389 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
390 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
391 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
392 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
393 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
394 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
395 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; }
396 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
398 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
407 int CL_ParticleEffectIndexForName(const char *name)
410 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
411 if (!strcmp(particleeffectname[i], name))
416 const char *CL_ParticleEffectNameForIndex(int i)
418 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
420 return particleeffectname[i];
423 // MUST match effectnameindex_t in client.h
424 static const char *standardeffectnames[EFFECT_TOTAL] =
448 "TE_TEI_BIGEXPLOSION",
464 void CL_Particles_LoadEffectInfo(void)
468 unsigned char *filedata;
469 fs_offset_t filesize;
470 char filename[MAX_QPATH];
471 numparticleeffectinfo = 0;
472 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
473 memset(particleeffectname, 0, sizeof(particleeffectname));
474 for (i = 0;i < EFFECT_TOTAL;i++)
475 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
476 for (filepass = 0;;filepass++)
479 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
480 else if (filepass == 1)
482 if (!cl.worldbasename[0])
484 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
488 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
491 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
501 void CL_ReadPointFile_f (void);
502 void CL_Particles_Init (void)
504 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)");
505 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
507 Cvar_RegisterVariable (&cl_particles);
508 Cvar_RegisterVariable (&cl_particles_quality);
509 Cvar_RegisterVariable (&cl_particles_alpha);
510 Cvar_RegisterVariable (&cl_particles_size);
511 Cvar_RegisterVariable (&cl_particles_quake);
512 Cvar_RegisterVariable (&cl_particles_blood);
513 Cvar_RegisterVariable (&cl_particles_blood_alpha);
514 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
515 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
516 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
517 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
518 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
519 Cvar_RegisterVariable (&cl_particles_explosions_shell);
520 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
521 Cvar_RegisterVariable (&cl_particles_rain);
522 Cvar_RegisterVariable (&cl_particles_snow);
523 Cvar_RegisterVariable (&cl_particles_smoke);
524 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
525 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
526 Cvar_RegisterVariable (&cl_particles_sparks);
527 Cvar_RegisterVariable (&cl_particles_bubbles);
528 Cvar_RegisterVariable (&cl_particles_visculling);
529 Cvar_RegisterVariable (&cl_particles_collisions);
530 Cvar_RegisterVariable (&cl_decals);
531 Cvar_RegisterVariable (&cl_decals_visculling);
532 Cvar_RegisterVariable (&cl_decals_time);
533 Cvar_RegisterVariable (&cl_decals_fadetime);
534 Cvar_RegisterVariable (&cl_decals_newsystem);
535 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
536 Cvar_RegisterVariable (&cl_decals_models);
537 Cvar_RegisterVariable (&cl_decals_bias);
538 Cvar_RegisterVariable (&cl_decals_max);
541 void CL_Particles_Shutdown (void)
545 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
546 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
548 // list of all 26 parameters:
549 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
550 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
551 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
552 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
553 // palpha - opacity of particle as 0-255 (can be more than 255)
554 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
555 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
556 // pgravity - how much effect gravity has on the particle (0-1)
557 // 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
558 // px,py,pz - starting origin of particle
559 // pvx,pvy,pvz - starting velocity of particle
560 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
561 // blendmode - one of the PBLEND_ values
562 // orientation - one of the PARTICLE_ values
563 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
564 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
565 // stainalpha: opacity of the stain as factor for alpha
566 // stainsize: size of the stain as factor for palpha
567 // angle: base rotation of the particle geometry around its center normal
568 // spin: rotation speed of the particle geometry around its center normal
569 particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin)
574 if (!cl_particles.integer)
576 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
577 if (cl.free_particle >= cl.max_particles)
580 lifetime = palpha / min(1, palphafade);
581 part = &cl.particles[cl.free_particle++];
582 if (cl.num_particles < cl.free_particle)
583 cl.num_particles = cl.free_particle;
584 memset(part, 0, sizeof(*part));
585 VectorCopy(sortorigin, part->sortorigin);
586 part->typeindex = ptypeindex;
587 part->blendmode = blendmode;
588 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
590 particletexture_t *tex = &particletexture[ptex];
591 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
592 part->orientation = PARTICLE_VBEAM;
594 part->orientation = PARTICLE_HBEAM;
597 part->orientation = orientation;
598 l2 = (int)lhrandom(0.5, 256.5);
600 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
601 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
602 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
603 part->staintexnum = staintex;
604 if(staincolor1 >= 0 && staincolor2 >= 0)
606 l2 = (int)lhrandom(0.5, 256.5);
608 if(blendmode == PBLEND_INVMOD)
610 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
611 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
612 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
616 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
617 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
618 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
620 if(r > 0xFF) r = 0xFF;
621 if(g > 0xFF) g = 0xFF;
622 if(b > 0xFF) b = 0xFF;
626 r = part->color[0]; // -1 is shorthand for stain = particle color
630 part->staincolor[0] = r;
631 part->staincolor[1] = g;
632 part->staincolor[2] = b;
633 part->stainalpha = palpha * stainalpha;
634 part->stainsize = psize * stainsize;
637 part->sizeincrease = psizeincrease;
638 part->alpha = palpha;
639 part->alphafade = palphafade;
640 part->gravity = pgravity;
641 part->bounce = pbounce;
642 part->stretch = stretch;
644 part->org[0] = px + originjitter * v[0];
645 part->org[1] = py + originjitter * v[1];
646 part->org[2] = pz + originjitter * v[2];
647 part->vel[0] = pvx + velocityjitter * v[0];
648 part->vel[1] = pvy + velocityjitter * v[1];
649 part->vel[2] = pvz + velocityjitter * v[2];
651 part->airfriction = pairfriction;
652 part->liquidfriction = pliquidfriction;
653 part->die = cl.time + lifetime;
654 part->delayedspawn = cl.time;
655 // part->delayedcollisions = 0;
656 part->qualityreduction = pqualityreduction;
659 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
660 if (part->typeindex == pt_rain)
664 float lifetime = part->die - cl.time;
667 // turn raindrop into simple spark and create delayedspawn splash effect
668 part->typeindex = pt_spark;
670 VectorMA(part->org, lifetime, part->vel, endvec);
671 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
672 part->die = cl.time + lifetime * trace.fraction;
673 part2 = CL_NewParticle(endvec, pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1, 1, 1, 0, 0);
676 part2->delayedspawn = part->die;
677 part2->die += part->die - cl.time;
678 for (i = rand() & 7;i < 10;i++)
680 part2 = CL_NewParticle(endvec, pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0);
683 part2->delayedspawn = part->die;
684 part2->die += part->die - cl.time;
690 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
692 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
695 VectorMA(part->org, lifetime, part->vel, endvec);
696 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
697 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
704 static void CL_ImmediateBloodStain(particle_t *part)
709 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
710 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
712 VectorCopy(part->vel, v);
714 staintex = part->staintexnum;
715 R_DecalSystem_SplatEntities(part->org, v, 1-part->staincolor[0]*(1.0f/255.0f), 1-part->staincolor[1]*(1.0f/255.0f), 1-part->staincolor[2]*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize);
718 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
719 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
721 VectorCopy(part->vel, v);
723 staintex = tex_blooddecal[rand()&7];
724 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);
728 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
732 entity_render_t *ent = &cl.entities[hitent].render;
733 unsigned char color[3];
734 if (!cl_decals.integer)
736 if (!ent->allowdecals)
739 l2 = (int)lhrandom(0.5, 256.5);
741 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
742 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
743 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
745 if (cl_decals_newsystem.integer)
747 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);
751 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
752 if (cl.free_decal >= cl.max_decals)
754 decal = &cl.decals[cl.free_decal++];
755 if (cl.num_decals < cl.free_decal)
756 cl.num_decals = cl.free_decal;
757 memset(decal, 0, sizeof(*decal));
758 decal->decalsequence = cl.decalsequence++;
759 decal->typeindex = pt_decal;
760 decal->texnum = texnum;
761 VectorMA(org, cl_decals_bias.value, normal, decal->org);
762 VectorCopy(normal, decal->normal);
764 decal->alpha = alpha;
765 decal->time2 = cl.time;
766 decal->color[0] = color[0];
767 decal->color[1] = color[1];
768 decal->color[2] = color[2];
769 decal->owner = hitent;
770 decal->clusterindex = -1000; // no vis culling unless we're sure
773 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
774 decal->ownermodel = cl.entities[decal->owner].render.model;
775 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
776 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
780 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
782 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
784 decal->clusterindex = leaf->clusterindex;
789 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
792 float bestfrac, bestorg[3], bestnormal[3];
794 int besthitent = 0, hitent;
797 for (i = 0;i < 32;i++)
800 VectorMA(org, maxdist, org2, org2);
801 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
802 // take the closest trace result that doesn't end up hitting a NOMARKS
803 // surface (sky for example)
804 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
806 bestfrac = trace.fraction;
808 VectorCopy(trace.endpos, bestorg);
809 VectorCopy(trace.plane.normal, bestnormal);
813 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
816 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
817 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
818 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)
821 matrix4x4_t tempmatrix;
823 VectorLerp(originmins, 0.5, originmaxs, center);
824 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
825 if (effectnameindex == EFFECT_SVC_PARTICLE)
827 if (cl_particles.integer)
829 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
831 CL_ParticleExplosion(center);
832 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
833 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
836 count *= cl_particles_quality.value;
837 for (;count > 0;count--)
839 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
840 CL_NewParticle(center, pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
845 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
846 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
847 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
848 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
849 else if (effectnameindex == EFFECT_TE_SPIKE)
851 if (cl_particles_bulletimpacts.integer)
853 if (cl_particles_quake.integer)
855 if (cl_particles_smoke.integer)
856 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
860 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
861 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
862 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
866 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
867 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
869 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
871 if (cl_particles_bulletimpacts.integer)
873 if (cl_particles_quake.integer)
875 if (cl_particles_smoke.integer)
876 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
880 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
881 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
882 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
886 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
887 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
888 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);
890 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
892 if (cl_particles_bulletimpacts.integer)
894 if (cl_particles_quake.integer)
896 if (cl_particles_smoke.integer)
897 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
901 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
902 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
903 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
907 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
908 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
910 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
912 if (cl_particles_bulletimpacts.integer)
914 if (cl_particles_quake.integer)
916 if (cl_particles_smoke.integer)
917 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
921 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
922 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
923 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
927 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
928 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
929 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);
931 else if (effectnameindex == EFFECT_TE_BLOOD)
933 if (!cl_particles_blood.integer)
935 if (cl_particles_quake.integer)
936 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
939 static double bloodaccumulator = 0;
940 qboolean immediatebloodstain = true;
941 //CL_NewParticle(center, pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
942 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
943 for (;bloodaccumulator > 0;bloodaccumulator--)
945 part = CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 1, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
946 if (immediatebloodstain && part)
948 immediatebloodstain = false;
949 CL_ImmediateBloodStain(part);
954 else if (effectnameindex == EFFECT_TE_SPARK)
955 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
956 else if (effectnameindex == EFFECT_TE_PLASMABURN)
958 // plasma scorch mark
959 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
960 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
961 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
963 else if (effectnameindex == EFFECT_TE_GUNSHOT)
965 if (cl_particles_bulletimpacts.integer)
967 if (cl_particles_quake.integer)
968 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
971 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
972 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
973 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
977 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
978 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
980 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
982 if (cl_particles_bulletimpacts.integer)
984 if (cl_particles_quake.integer)
985 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
988 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
989 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
990 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
994 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
995 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
996 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);
998 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1000 CL_ParticleExplosion(center);
1001 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);
1003 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1005 CL_ParticleExplosion(center);
1006 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);
1008 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1010 if (cl_particles_quake.integer)
1013 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1016 CL_NewParticle(center, pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1018 CL_NewParticle(center, pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1022 CL_ParticleExplosion(center);
1023 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);
1025 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1026 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);
1027 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1029 count *= cl_particles_quality.value;
1031 CL_NewParticle(center, pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1033 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1035 float i, j, inc, vel;
1038 inc = 8 / cl_particles_quality.value;
1039 for (i = -128;i < 128;i += inc)
1041 for (j = -128;j < 128;j += inc)
1043 dir[0] = j + lhrandom(0, inc);
1044 dir[1] = i + lhrandom(0, inc);
1046 org[0] = center[0] + dir[0];
1047 org[1] = center[1] + dir[1];
1048 org[2] = center[2] + lhrandom(0, 64);
1049 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1050 CL_NewParticle(center, pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1054 else if (effectnameindex == EFFECT_TE_TELEPORT)
1056 float i, j, k, inc, vel;
1059 if (cl_particles_quake.integer)
1060 inc = 4 / cl_particles_quality.value;
1062 inc = 8 / cl_particles_quality.value;
1063 for (i = -16;i < 16;i += inc)
1065 for (j = -16;j < 16;j += inc)
1067 for (k = -24;k < 32;k += inc)
1069 VectorSet(dir, i*8, j*8, k*8);
1070 VectorNormalize(dir);
1071 vel = lhrandom(50, 113);
1072 if (cl_particles_quake.integer)
1073 CL_NewParticle(center, pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1075 CL_NewParticle(center, pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1079 if (!cl_particles_quake.integer)
1080 CL_NewParticle(center, pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1081 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);
1083 else if (effectnameindex == EFFECT_TE_TEI_G3)
1084 CL_NewParticle(center, pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0);
1085 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1087 if (cl_particles_smoke.integer)
1089 count *= 0.25f * cl_particles_quality.value;
1091 CL_NewParticle(center, pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1094 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1096 CL_ParticleExplosion(center);
1097 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);
1099 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1102 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1103 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1104 if (cl_particles_smoke.integer)
1105 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1106 CL_NewParticle(center, pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1107 if (cl_particles_sparks.integer)
1108 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1109 CL_NewParticle(center, pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0);
1110 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);
1112 else if (effectnameindex == EFFECT_EF_FLAME)
1114 count *= 300 * cl_particles_quality.value;
1116 CL_NewParticle(center, pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1117 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);
1119 else if (effectnameindex == EFFECT_EF_STARDUST)
1121 count *= 200 * cl_particles_quality.value;
1123 CL_NewParticle(center, pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1124 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);
1126 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1130 int smoke, blood, bubbles, r, color;
1132 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1135 Vector4Set(light, 0, 0, 0, 0);
1137 if (effectnameindex == EFFECT_TR_ROCKET)
1138 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1139 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1141 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1142 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1144 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1146 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1147 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1151 matrix4x4_t tempmatrix;
1152 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1153 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);
1154 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1158 if (!spawnparticles)
1161 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1164 VectorSubtract(originmaxs, originmins, dir);
1165 len = VectorNormalizeLength(dir);
1168 dec = -ent->persistent.trail_time;
1169 ent->persistent.trail_time += len;
1170 if (ent->persistent.trail_time < 0.01f)
1173 // if we skip out, leave it reset
1174 ent->persistent.trail_time = 0.0f;
1179 // advance into this frame to reach the first puff location
1180 VectorMA(originmins, dec, dir, pos);
1183 smoke = cl_particles.integer && cl_particles_smoke.integer;
1184 blood = cl_particles.integer && cl_particles_blood.integer;
1185 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1186 qd = 1.0f / cl_particles_quality.value;
1193 if (effectnameindex == EFFECT_TR_BLOOD)
1195 if (cl_particles_quake.integer)
1197 color = particlepalette[67 + (rand()&3)];
1198 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1203 CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1206 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1208 if (cl_particles_quake.integer)
1211 color = particlepalette[67 + (rand()&3)];
1212 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1217 CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1223 if (effectnameindex == EFFECT_TR_ROCKET)
1225 if (cl_particles_quake.integer)
1228 color = particlepalette[ramp3[r]];
1229 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1233 CL_NewParticle(center, pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1234 CL_NewParticle(center, pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1237 else if (effectnameindex == EFFECT_TR_GRENADE)
1239 if (cl_particles_quake.integer)
1242 color = particlepalette[ramp3[r]];
1243 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1247 CL_NewParticle(center, pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1250 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1252 if (cl_particles_quake.integer)
1255 color = particlepalette[52 + (rand()&7)];
1256 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1257 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1259 else if (gamemode == GAME_GOODVSBAD2)
1262 CL_NewParticle(center, pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1266 color = particlepalette[20 + (rand()&7)];
1267 CL_NewParticle(center, pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1270 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1272 if (cl_particles_quake.integer)
1275 color = particlepalette[230 + (rand()&7)];
1276 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1277 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1281 color = particlepalette[226 + (rand()&7)];
1282 CL_NewParticle(center, pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1285 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1287 if (cl_particles_quake.integer)
1289 color = particlepalette[152 + (rand()&3)];
1290 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1292 else if (gamemode == GAME_GOODVSBAD2)
1295 CL_NewParticle(center, pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1297 else if (gamemode == GAME_PRYDON)
1300 CL_NewParticle(center, pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1303 CL_NewParticle(center, pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1305 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1308 CL_NewParticle(center, pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1310 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1313 CL_NewParticle(center, pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1315 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1316 CL_NewParticle(center, pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1320 if (effectnameindex == EFFECT_TR_ROCKET)
1321 CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1322 else if (effectnameindex == EFFECT_TR_GRENADE)
1323 CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1325 // advance to next time and position
1328 VectorMA (pos, dec, dir, pos);
1331 ent->persistent.trail_time = len;
1334 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1337 // this is also called on point effects with spawndlight = true and
1338 // spawnparticles = true
1339 // it is called CL_ParticleTrail because most code does not want to supply
1340 // these parameters, only trail handling does
1341 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)
1343 qboolean found = false;
1344 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1346 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1347 return; // no such effect
1349 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1351 int effectinfoindex;
1354 particleeffectinfo_t *info;
1361 qboolean underwater;
1362 qboolean immediatebloodstain;
1364 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1365 VectorLerp(originmins, 0.5, originmaxs, center);
1366 supercontents = CL_PointSuperContents(center);
1367 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1368 VectorSubtract(originmaxs, originmins, traildir);
1369 traillen = VectorLength(traildir);
1370 VectorNormalize(traildir);
1371 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1373 if (info->effectnameindex == effectnameindex)
1376 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1378 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1381 // spawn a dlight if requested
1382 if (info->lightradiusstart > 0 && spawndlight)
1384 matrix4x4_t tempmatrix;
1385 if (info->trailspacing > 0)
1386 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1388 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1389 if (info->lighttime > 0 && info->lightradiusfade > 0)
1391 // light flash (explosion, etc)
1392 // called when effect starts
1393 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);
1395 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1398 // called by CL_LinkNetworkEntity
1399 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1400 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);
1401 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1405 if (!spawnparticles)
1410 if (info->tex[1] > info->tex[0])
1412 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1413 tex = min(tex, info->tex[1] - 1);
1415 if(info->staintex[0] < 0)
1416 staintex = info->staintex[0];
1419 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1420 staintex = min(staintex, info->staintex[1] - 1);
1422 if (info->particletype == pt_decal)
1423 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]);
1424 else if (info->orientation == PARTICLE_HBEAM)
1425 CL_NewParticle(center, info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], 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, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), 0, 0);
1428 if (!cl_particles.integer)
1430 switch (info->particletype)
1432 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1433 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1434 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1435 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1436 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1437 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1440 VectorCopy(originmins, trailpos);
1441 if (info->trailspacing > 0)
1443 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1444 trailstep = info->trailspacing / cl_particles_quality.value;
1445 immediatebloodstain = false;
1449 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1451 immediatebloodstain = info->particletype == pt_blood || staintex;
1453 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1454 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1456 if (info->tex[1] > info->tex[0])
1458 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1459 tex = min(tex, info->tex[1] - 1);
1463 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1464 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1465 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1468 part = CL_NewParticle(center, info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], 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, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), lhrandom(info->rotate[0], info->rotate[1]), lhrandom(info->rotate[2], info->rotate[3]));
1469 if (immediatebloodstain && part)
1471 immediatebloodstain = false;
1472 CL_ImmediateBloodStain(part);
1475 VectorMA(trailpos, trailstep, traildir, trailpos);
1482 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1485 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)
1487 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1495 void CL_EntityParticles (const entity_t *ent)
1498 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1499 static vec3_t avelocities[NUMVERTEXNORMALS];
1500 if (!cl_particles.integer) return;
1501 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1503 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1505 if (!avelocities[0][0])
1506 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1507 avelocities[0][i] = lhrandom(0, 2.55);
1509 for (i = 0;i < NUMVERTEXNORMALS;i++)
1511 yaw = cl.time * avelocities[i][0];
1512 pitch = cl.time * avelocities[i][1];
1513 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1514 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1515 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1516 CL_NewParticle(org, pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1521 void CL_ReadPointFile_f (void)
1523 vec3_t org, leakorg;
1525 char *pointfile = NULL, *pointfilepos, *t, tchar;
1526 char name[MAX_QPATH];
1531 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1532 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1535 Con_Printf("Could not open %s\n", name);
1539 Con_Printf("Reading %s...\n", name);
1540 VectorClear(leakorg);
1543 pointfilepos = pointfile;
1544 while (*pointfilepos)
1546 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1551 while (*t && *t != '\n' && *t != '\r')
1555 #if _MSC_VER >= 1400
1556 #define sscanf sscanf_s
1558 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1564 VectorCopy(org, leakorg);
1567 if (cl.num_particles < cl.max_particles - 3)
1570 CL_NewParticle(org, pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1573 Mem_Free(pointfile);
1574 VectorCopy(leakorg, org);
1575 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1577 CL_NewParticle(org, pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0);
1578 CL_NewParticle(org, pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0);
1579 CL_NewParticle(org, pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0);
1584 CL_ParseParticleEffect
1586 Parse an effect out of the server message
1589 void CL_ParseParticleEffect (void)
1592 int i, count, msgcount, color;
1594 MSG_ReadVector(org, cls.protocol);
1595 for (i=0 ; i<3 ; i++)
1596 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1597 msgcount = MSG_ReadByte ();
1598 color = MSG_ReadByte ();
1600 if (msgcount == 255)
1605 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1610 CL_ParticleExplosion
1614 void CL_ParticleExplosion (const vec3_t org)
1620 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1621 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1623 if (cl_particles_quake.integer)
1625 for (i = 0;i < 1024;i++)
1631 color = particlepalette[ramp1[r]];
1632 CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1636 color = particlepalette[ramp2[r]];
1637 CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1643 i = CL_PointSuperContents(org);
1644 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1646 if (cl_particles.integer && cl_particles_bubbles.integer)
1647 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1648 CL_NewParticle(org, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1652 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1654 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1658 for (k = 0;k < 16;k++)
1661 VectorMA(org, 128, v2, v);
1662 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1663 if (trace.fraction >= 0.1)
1666 VectorSubtract(trace.endpos, org, v2);
1667 VectorScale(v2, 2.0f, v2);
1668 CL_NewParticle(org, pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0);
1674 if (cl_particles_explosions_shell.integer)
1675 R_NewExplosion(org);
1680 CL_ParticleExplosion2
1684 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1687 if (!cl_particles.integer) return;
1689 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1691 k = particlepalette[colorStart + (i % colorLength)];
1692 if (cl_particles_quake.integer)
1693 CL_NewParticle(org, pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1695 CL_NewParticle(org, pt_alphastatic, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1699 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1702 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1703 if (cl_particles_sparks.integer)
1705 sparkcount *= cl_particles_quality.value;
1706 while(sparkcount-- > 0)
1707 CL_NewParticle(center, pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0);
1711 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1714 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1715 if (cl_particles_smoke.integer)
1717 smokecount *= cl_particles_quality.value;
1718 while(smokecount-- > 0)
1719 CL_NewParticle(center, pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1723 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)
1727 if (!cl_particles.integer) return;
1728 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1730 count = (int)(count * cl_particles_quality.value);
1733 k = particlepalette[colorbase + (rand()&3)];
1734 CL_NewParticle(center, pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1738 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1741 float minz, maxz, lifetime = 30;
1743 if (!cl_particles.integer) return;
1744 if (dir[2] < 0) // falling
1746 minz = maxs[2] + dir[2] * 0.1;
1749 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1754 maxz = maxs[2] + dir[2] * 0.1;
1756 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1759 count = (int)(count * cl_particles_quality.value);
1764 if (!cl_particles_rain.integer) break;
1765 count *= 4; // ick, this should be in the mod or maps?
1769 k = particlepalette[colorbase + (rand()&3)];
1770 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1771 if (gamemode == GAME_GOODVSBAD2)
1772 CL_NewParticle(org, pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0);
1774 CL_NewParticle(org, pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0);
1778 if (!cl_particles_snow.integer) break;
1781 k = particlepalette[colorbase + (rand()&3)];
1782 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1783 if (gamemode == GAME_GOODVSBAD2)
1784 CL_NewParticle(org, pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1786 CL_NewParticle(org, pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1790 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1794 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1795 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1796 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1797 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1799 #define PARTICLETEXTURESIZE 64
1800 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1802 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1806 dz = 1 - (dx*dx+dy*dy);
1807 if (dz > 0) // it does hit the sphere
1811 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1812 VectorNormalize(normal);
1813 dot = DotProduct(normal, light);
1814 if (dot > 0.5) // interior reflection
1815 f += ((dot * 2) - 1);
1816 else if (dot < -0.5) // exterior reflection
1817 f += ((dot * -2) - 1);
1819 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1820 VectorNormalize(normal);
1821 dot = DotProduct(normal, light);
1822 if (dot > 0.5) // interior reflection
1823 f += ((dot * 2) - 1);
1824 else if (dot < -0.5) // exterior reflection
1825 f += ((dot * -2) - 1);
1827 f += 16; // just to give it a haze so you can see the outline
1828 f = bound(0, f, 255);
1829 return (unsigned char) f;
1835 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1836 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1838 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1839 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1840 *width = particlefontcellwidth;
1841 *height = particlefontcellheight;
1844 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1846 int basex, basey, w, h, y;
1847 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1848 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1849 Sys_Error("invalid particle texture size for autogenerating");
1850 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1851 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1854 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1857 float cx, cy, dx, dy, f, iradius;
1859 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1860 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1861 iradius = 1.0f / radius;
1862 alpha *= (1.0f / 255.0f);
1863 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1865 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1869 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1874 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1875 d[0] += (int)(f * (blue - d[0]));
1876 d[1] += (int)(f * (green - d[1]));
1877 d[2] += (int)(f * (red - d[2]));
1883 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1886 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1888 data[0] = bound(minb, data[0], maxb);
1889 data[1] = bound(ming, data[1], maxg);
1890 data[2] = bound(minr, data[2], maxr);
1894 void particletextureinvert(unsigned char *data)
1897 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1899 data[0] = 255 - data[0];
1900 data[1] = 255 - data[1];
1901 data[2] = 255 - data[2];
1905 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1906 static void R_InitBloodTextures (unsigned char *particletexturedata)
1909 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1910 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1913 for (i = 0;i < 8;i++)
1915 memset(data, 255, datasize);
1916 for (k = 0;k < 24;k++)
1917 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1918 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1919 particletextureinvert(data);
1920 setuptex(tex_bloodparticle[i], data, particletexturedata);
1924 for (i = 0;i < 8;i++)
1926 memset(data, 255, datasize);
1928 for (j = 1;j < 10;j++)
1929 for (k = min(j, m - 1);k < m;k++)
1930 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1931 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1932 particletextureinvert(data);
1933 setuptex(tex_blooddecal[i], data, particletexturedata);
1939 //uncomment this to make engine save out particle font to a tga file when run
1940 //#define DUMPPARTICLEFONT
1942 static void R_InitParticleTexture (void)
1944 int x, y, d, i, k, m;
1945 int basex, basey, w, h;
1946 float dx, dy, f, s1, t1, s2, t2;
1949 fs_offset_t filesize;
1950 char texturename[MAX_QPATH];
1952 // a note: decals need to modulate (multiply) the background color to
1953 // properly darken it (stain), and they need to be able to alpha fade,
1954 // this is a very difficult challenge because it means fading to white
1955 // (no change to background) rather than black (darkening everything
1956 // behind the whole decal polygon), and to accomplish this the texture is
1957 // inverted (dark red blood on white background becomes brilliant cyan
1958 // and white on black background) so we can alpha fade it to black, then
1959 // we invert it again during the blendfunc to make it work...
1961 #ifndef DUMPPARTICLEFONT
1962 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1965 particlefonttexture = decalskinframe->base;
1966 // TODO maybe allow custom grid size?
1967 particlefontwidth = image_width;
1968 particlefontheight = image_height;
1969 particlefontcellwidth = image_width / 8;
1970 particlefontcellheight = image_height / 8;
1971 particlefontcols = 8;
1972 particlefontrows = 8;
1977 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1978 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1979 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1980 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1981 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1983 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1984 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1985 particlefontcols = 8;
1986 particlefontrows = 8;
1988 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1991 for (i = 0;i < 8;i++)
1993 memset(data, 255, datasize);
1996 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1997 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1999 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2001 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2002 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2004 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2005 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2007 d = (int)(d * (1-(dx*dx+dy*dy)));
2008 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2009 d = bound(0, d, 255);
2010 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2017 setuptex(tex_smoke[i], data, particletexturedata);
2021 memset(data, 255, datasize);
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 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2029 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2032 setuptex(tex_rainsplash, data, particletexturedata);
2035 memset(data, 255, datasize);
2036 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2038 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2039 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2041 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2042 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2043 d = bound(0, d, 255);
2044 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2047 setuptex(tex_particle, data, particletexturedata);
2050 memset(data, 255, datasize);
2051 light[0] = 1;light[1] = 1;light[2] = 1;
2052 VectorNormalize(light);
2053 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2055 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2056 // stretch upper half of bubble by +50% and shrink lower half by -50%
2057 // (this gives an elongated teardrop shape)
2059 dy = (dy - 0.5f) * 2.0f;
2061 dy = (dy - 0.5f) / 1.5f;
2062 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2064 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2065 // shrink bubble width to half
2067 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2070 setuptex(tex_raindrop, data, particletexturedata);
2073 memset(data, 255, datasize);
2074 light[0] = 1;light[1] = 1;light[2] = 1;
2075 VectorNormalize(light);
2076 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2078 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2079 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2081 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2082 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2085 setuptex(tex_bubble, data, particletexturedata);
2087 // Blood particles and blood decals
2088 R_InitBloodTextures (particletexturedata);
2091 for (i = 0;i < 8;i++)
2093 memset(data, 255, datasize);
2094 for (k = 0;k < 12;k++)
2095 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2096 for (k = 0;k < 3;k++)
2097 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2098 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2099 particletextureinvert(data);
2100 setuptex(tex_bulletdecal[i], data, particletexturedata);
2103 #ifdef DUMPPARTICLEFONT
2104 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2107 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2108 particlefonttexture = decalskinframe->base;
2110 Mem_Free(particletexturedata);
2115 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2117 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2118 particletexture[i].texture = particlefonttexture;
2119 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2120 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2121 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2122 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2125 #ifndef DUMPPARTICLEFONT
2126 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
2127 if (!particletexture[tex_beam].texture)
2130 unsigned char noise3[64][64], data2[64][16][4];
2132 fractalnoise(&noise3[0][0], 64, 4);
2134 for (y = 0;y < 64;y++)
2136 dy = (y - 0.5f*64) / (64*0.5f-1);
2137 for (x = 0;x < 16;x++)
2139 dx = (x - 0.5f*16) / (16*0.5f-2);
2140 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2141 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2142 data2[y][x][3] = 255;
2146 #ifdef DUMPPARTICLEFONT
2147 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2149 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2151 particletexture[tex_beam].s1 = 0;
2152 particletexture[tex_beam].t1 = 0;
2153 particletexture[tex_beam].s2 = 1;
2154 particletexture[tex_beam].t2 = 1;
2156 // now load an texcoord/texture override file
2157 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2164 if(!COM_ParseToken_Simple(&bufptr, true, false))
2166 if(!strcmp(com_token, "\n"))
2167 continue; // empty line
2168 i = atoi(com_token);
2176 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2178 s1 = atof(com_token);
2179 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2181 t1 = atof(com_token);
2182 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2184 s2 = atof(com_token);
2185 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2187 t2 = atof(com_token);
2188 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2189 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2190 strlcpy(texturename, com_token, sizeof(texturename));
2197 strlcpy(texturename, com_token, sizeof(texturename));
2200 if (!texturename[0])
2202 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2205 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2207 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2210 particletexture[i].texture = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR, false)->base;
2211 particletexture[i].s1 = s1;
2212 particletexture[i].t1 = t1;
2213 particletexture[i].s2 = s2;
2214 particletexture[i].t2 = t2;
2220 static void r_part_start(void)
2223 // generate particlepalette for convenience from the main one
2224 for (i = 0;i < 256;i++)
2225 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2226 particletexturepool = R_AllocTexturePool();
2227 R_InitParticleTexture ();
2228 CL_Particles_LoadEffectInfo();
2231 static void r_part_shutdown(void)
2233 R_FreeTexturePool(&particletexturepool);
2236 static void r_part_newmap(void)
2239 R_SkinFrame_MarkUsed(decalskinframe);
2240 CL_Particles_LoadEffectInfo();
2243 #define BATCHSIZE 256
2244 unsigned short particle_elements[BATCHSIZE*6];
2245 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2247 void R_Particles_Init (void)
2250 for (i = 0;i < BATCHSIZE;i++)
2252 particle_elements[i*6+0] = i*4+0;
2253 particle_elements[i*6+1] = i*4+1;
2254 particle_elements[i*6+2] = i*4+2;
2255 particle_elements[i*6+3] = i*4+0;
2256 particle_elements[i*6+4] = i*4+2;
2257 particle_elements[i*6+5] = i*4+3;
2260 Cvar_RegisterVariable(&r_drawparticles);
2261 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2262 Cvar_RegisterVariable(&r_drawdecals);
2263 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2264 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2267 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2269 int surfacelistindex;
2271 float *v3f, *t2f, *c4f;
2272 particletexture_t *tex;
2273 float right[3], up[3], size, ca;
2274 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2276 RSurf_ActiveWorldEntity();
2278 r_refdef.stats.drawndecals += numsurfaces;
2279 R_Mesh_ResetTextureState();
2280 GL_DepthMask(false);
2281 GL_DepthRange(0, 1);
2282 GL_PolygonOffset(0, 0);
2284 GL_CullFace(GL_NONE);
2286 // generate all the vertices at once
2287 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2289 d = cl.decals + surfacelist[surfacelistindex];
2292 c4f = particle_color4f + 16*surfacelistindex;
2293 ca = d->alpha * alphascale;
2294 // ensure alpha multiplier saturates properly
2295 if (ca > 1.0f / 256.0f)
2297 if (r_refdef.fogenabled)
2298 ca *= RSurf_FogVertex(d->org);
2299 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2300 Vector4Copy(c4f, c4f + 4);
2301 Vector4Copy(c4f, c4f + 8);
2302 Vector4Copy(c4f, c4f + 12);
2304 // calculate vertex positions
2305 size = d->size * cl_particles_size.value;
2306 VectorVectors(d->normal, right, up);
2307 VectorScale(right, size, right);
2308 VectorScale(up, size, up);
2309 v3f = particle_vertex3f + 12*surfacelistindex;
2310 v3f[ 0] = d->org[0] - right[0] - up[0];
2311 v3f[ 1] = d->org[1] - right[1] - up[1];
2312 v3f[ 2] = d->org[2] - right[2] - up[2];
2313 v3f[ 3] = d->org[0] - right[0] + up[0];
2314 v3f[ 4] = d->org[1] - right[1] + up[1];
2315 v3f[ 5] = d->org[2] - right[2] + up[2];
2316 v3f[ 6] = d->org[0] + right[0] + up[0];
2317 v3f[ 7] = d->org[1] + right[1] + up[1];
2318 v3f[ 8] = d->org[2] + right[2] + up[2];
2319 v3f[ 9] = d->org[0] + right[0] - up[0];
2320 v3f[10] = d->org[1] + right[1] - up[1];
2321 v3f[11] = d->org[2] + right[2] - up[2];
2323 // calculate texcoords
2324 tex = &particletexture[d->texnum];
2325 t2f = particle_texcoord2f + 8*surfacelistindex;
2326 t2f[0] = tex->s1;t2f[1] = tex->t2;
2327 t2f[2] = tex->s1;t2f[3] = tex->t1;
2328 t2f[4] = tex->s2;t2f[5] = tex->t1;
2329 t2f[6] = tex->s2;t2f[7] = tex->t2;
2332 // now render the decals all at once
2333 // (this assumes they all use one particle font texture!)
2334 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2335 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2336 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2337 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2340 void R_DrawDecals (void)
2343 int drawdecals = r_drawdecals.integer;
2348 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2350 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2351 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2353 // LordHavoc: early out conditions
2357 decalfade = frametime * 256 / cl_decals_fadetime.value;
2358 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2359 drawdist2 = drawdist2*drawdist2;
2361 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2363 if (!decal->typeindex)
2366 if (killsequence - decal->decalsequence > 0)
2369 if (cl.time > decal->time2 + cl_decals_time.value)
2371 decal->alpha -= decalfade;
2372 if (decal->alpha <= 0)
2378 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2380 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2381 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2387 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2393 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))
2394 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2397 decal->typeindex = 0;
2398 if (cl.free_decal > i)
2402 // reduce cl.num_decals if possible
2403 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2406 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2408 decal_t *olddecals = cl.decals;
2409 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2410 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2411 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2412 Mem_Free(olddecals);
2415 r_refdef.stats.totaldecals = cl.num_decals;
2418 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2420 int surfacelistindex;
2421 int batchstart, batchcount;
2422 const particle_t *p;
2424 rtexture_t *texture;
2425 float *v3f, *t2f, *c4f;
2426 particletexture_t *tex;
2427 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2428 float ambient[3], diffuse[3], diffusenormal[3];
2429 float spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2430 vec4_t colormultiplier;
2432 RSurf_ActiveWorldEntity();
2434 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));
2436 r_refdef.stats.particles += numsurfaces;
2437 R_Mesh_ResetTextureState();
2438 GL_DepthMask(false);
2439 GL_DepthRange(0, 1);
2440 GL_PolygonOffset(0, 0);
2442 GL_AlphaTest(false);
2443 GL_CullFace(GL_NONE);
2445 spintime = r_refdef.scene.time;
2447 // first generate all the vertices at once
2448 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2450 p = cl.particles + surfacelist[surfacelistindex];
2452 blendmode = (pblend_t)p->blendmode;
2456 case PBLEND_INVALID:
2458 alpha = p->alpha * colormultiplier[3];
2459 // ensure alpha multiplier saturates properly
2462 // additive and modulate can just fade out in fog (this is correct)
2463 if (r_refdef.fogenabled)
2464 alpha *= RSurf_FogVertex(p->org);
2465 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2466 alpha *= 1.0f / 256.0f;
2467 c4f[0] = p->color[0] * alpha;
2468 c4f[1] = p->color[1] * alpha;
2469 c4f[2] = p->color[2] * alpha;
2473 alpha = p->alpha * colormultiplier[3];
2474 // ensure alpha multiplier saturates properly
2477 // additive and modulate can just fade out in fog (this is correct)
2478 if (r_refdef.fogenabled)
2479 alpha *= RSurf_FogVertex(p->org);
2480 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2481 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2482 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2483 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2487 c4f[0] = p->color[0] * colormultiplier[0];
2488 c4f[1] = p->color[1] * colormultiplier[1];
2489 c4f[2] = p->color[2] * colormultiplier[2];
2490 c4f[3] = p->alpha * colormultiplier[3];
2491 // note: lighting is not cheap!
2492 if (particletype[p->typeindex].lighting)
2494 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2495 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2496 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2497 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2499 // mix in the fog color
2500 if (r_refdef.fogenabled)
2502 fog = RSurf_FogVertex(p->org);
2504 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2505 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2506 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2510 // copy the color into the other three vertices
2511 Vector4Copy(c4f, c4f + 4);
2512 Vector4Copy(c4f, c4f + 8);
2513 Vector4Copy(c4f, c4f + 12);
2515 size = p->size * cl_particles_size.value;
2516 tex = &particletexture[p->texnum];
2517 switch(p->orientation)
2519 // case PARTICLE_INVALID:
2520 case PARTICLE_BILLBOARD:
2521 if (p->angle + p->spin)
2523 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2524 spinsin = sin(spinrad) * size;
2525 spincos = cos(spinrad) * size;
2526 spinm1 = -p->stretch * spincos;
2529 spinm4 = -p->stretch * spincos;
2530 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2531 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2535 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2536 VectorScale(r_refdef.view.up, size, up);
2539 v3f[ 0] = p->org[0] - right[0] - up[0];
2540 v3f[ 1] = p->org[1] - right[1] - up[1];
2541 v3f[ 2] = p->org[2] - right[2] - up[2];
2542 v3f[ 3] = p->org[0] - right[0] + up[0];
2543 v3f[ 4] = p->org[1] - right[1] + up[1];
2544 v3f[ 5] = p->org[2] - right[2] + up[2];
2545 v3f[ 6] = p->org[0] + right[0] + up[0];
2546 v3f[ 7] = p->org[1] + right[1] + up[1];
2547 v3f[ 8] = p->org[2] + right[2] + up[2];
2548 v3f[ 9] = p->org[0] + right[0] - up[0];
2549 v3f[10] = p->org[1] + right[1] - up[1];
2550 v3f[11] = p->org[2] + right[2] - up[2];
2551 t2f[0] = tex->s1;t2f[1] = tex->t2;
2552 t2f[2] = tex->s1;t2f[3] = tex->t1;
2553 t2f[4] = tex->s2;t2f[5] = tex->t1;
2554 t2f[6] = tex->s2;t2f[7] = tex->t2;
2556 case PARTICLE_ORIENTED_DOUBLESIDED:
2557 VectorVectors(p->vel, baseright, baseup);
2558 if (p->angle + p->spin)
2560 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2561 spinsin = sin(spinrad) * size;
2562 spincos = cos(spinrad) * size;
2563 spinm1 = p->stretch * spincos;
2566 spinm4 = p->stretch * spincos;
2567 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2568 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2572 VectorScale(baseright, size * p->stretch, right);
2573 VectorScale(baseup, size, up);
2575 v3f[ 0] = p->org[0] - right[0] - up[0];
2576 v3f[ 1] = p->org[1] - right[1] - up[1];
2577 v3f[ 2] = p->org[2] - right[2] - up[2];
2578 v3f[ 3] = p->org[0] - right[0] + up[0];
2579 v3f[ 4] = p->org[1] - right[1] + up[1];
2580 v3f[ 5] = p->org[2] - right[2] + up[2];
2581 v3f[ 6] = p->org[0] + right[0] + up[0];
2582 v3f[ 7] = p->org[1] + right[1] + up[1];
2583 v3f[ 8] = p->org[2] + right[2] + up[2];
2584 v3f[ 9] = p->org[0] + right[0] - up[0];
2585 v3f[10] = p->org[1] + right[1] - up[1];
2586 v3f[11] = p->org[2] + right[2] - up[2];
2587 t2f[0] = tex->s1;t2f[1] = tex->t2;
2588 t2f[2] = tex->s1;t2f[3] = tex->t1;
2589 t2f[4] = tex->s2;t2f[5] = tex->t1;
2590 t2f[6] = tex->s2;t2f[7] = tex->t2;
2592 case PARTICLE_SPARK:
2593 len = VectorLength(p->vel);
2594 VectorNormalize2(p->vel, up);
2595 lenfactor = p->stretch * 0.04 * len;
2596 if(lenfactor < size * 0.5)
2597 lenfactor = size * 0.5;
2598 VectorMA(p->org, -lenfactor, up, v);
2599 VectorMA(p->org, lenfactor, up, up2);
2600 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2601 t2f[0] = tex->s1;t2f[1] = tex->t2;
2602 t2f[2] = tex->s1;t2f[3] = tex->t1;
2603 t2f[4] = tex->s2;t2f[5] = tex->t1;
2604 t2f[6] = tex->s2;t2f[7] = tex->t2;
2606 case PARTICLE_VBEAM:
2607 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2608 VectorSubtract(p->vel, p->org, up);
2609 VectorNormalize(up);
2610 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2611 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2612 t2f[0] = tex->s2;t2f[1] = v[0];
2613 t2f[2] = tex->s1;t2f[3] = v[0];
2614 t2f[4] = tex->s1;t2f[5] = v[1];
2615 t2f[6] = tex->s2;t2f[7] = v[1];
2617 case PARTICLE_HBEAM:
2618 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2619 VectorSubtract(p->vel, p->org, up);
2620 VectorNormalize(up);
2621 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2622 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2623 t2f[0] = v[0];t2f[1] = tex->t1;
2624 t2f[2] = v[0];t2f[3] = tex->t2;
2625 t2f[4] = v[1];t2f[5] = tex->t2;
2626 t2f[6] = v[1];t2f[7] = tex->t1;
2631 // now render batches of particles based on blendmode and texture
2632 blendmode = PBLEND_INVALID;
2636 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2637 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2639 p = cl.particles + surfacelist[surfacelistindex];
2641 if (blendmode != p->blendmode)
2643 blendmode = (pblend_t)p->blendmode;
2647 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2649 case PBLEND_INVALID:
2651 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2654 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2658 if (texture != particletexture[p->texnum].texture)
2660 texture = particletexture[p->texnum].texture;
2661 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2664 // iterate until we find a change in settings
2665 batchstart = surfacelistindex++;
2666 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2668 p = cl.particles + surfacelist[surfacelistindex];
2669 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2673 batchcount = surfacelistindex - batchstart;
2674 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2678 void R_DrawParticles (void)
2681 int drawparticles = r_drawparticles.integer;
2682 float minparticledist;
2684 float gravity, frametime, f, dist, oldorg[3];
2690 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2691 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2693 // LordHavoc: early out conditions
2694 if (!cl.num_particles)
2697 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2698 gravity = frametime * cl.movevars_gravity;
2699 update = frametime > 0;
2700 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2701 drawdist2 = drawdist2*drawdist2;
2703 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2707 if (cl.free_particle > i)
2708 cl.free_particle = i;
2714 if (p->delayedspawn > cl.time)
2717 p->size += p->sizeincrease * frametime;
2718 p->alpha -= p->alphafade * frametime;
2720 if (p->alpha <= 0 || p->die <= cl.time)
2723 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2725 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2727 if (p->typeindex == pt_blood)
2728 p->size += frametime * 8;
2730 p->vel[2] -= p->gravity * gravity;
2731 f = 1.0f - min(p->liquidfriction * frametime, 1);
2732 VectorScale(p->vel, f, p->vel);
2736 p->vel[2] -= p->gravity * gravity;
2739 f = 1.0f - min(p->airfriction * frametime, 1);
2740 VectorScale(p->vel, f, p->vel);
2744 VectorCopy(p->org, oldorg);
2745 VectorMA(p->org, frametime, p->vel, p->org);
2746 // if (p->bounce && cl.time >= p->delayedcollisions)
2747 if (p->bounce && cl_particles_collisions.integer)
2749 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);
2750 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2751 // or if the trace hit something flagged as NOIMPACT
2752 // then remove the particle
2753 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2755 VectorCopy(trace.endpos, p->org);
2756 // react if the particle hit something
2757 if (trace.fraction < 1)
2759 VectorCopy(trace.endpos, p->org);
2761 if (p->staintexnum >= 0)
2763 // blood - splash on solid
2764 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2767 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2768 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2769 if (cl_decals.integer)
2771 // create a decal for the blood splat
2772 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2773 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2778 if (p->typeindex == pt_blood)
2780 // blood - splash on solid
2781 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2783 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2785 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)));
2786 if (cl_decals.integer)
2788 // create a decal for the blood splat
2789 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 * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768);
2794 else if (p->bounce < 0)
2796 // bounce -1 means remove on impact
2801 // anything else - bounce off solid
2802 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2803 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2804 if (DotProduct(p->vel, p->vel) < 0.03)
2805 VectorClear(p->vel);
2811 if (p->typeindex != pt_static)
2813 switch (p->typeindex)
2815 case pt_entityparticle:
2816 // particle that removes itself after one rendered frame
2823 a = CL_PointSuperContents(p->org);
2824 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2828 a = CL_PointSuperContents(p->org);
2829 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2833 a = CL_PointSuperContents(p->org);
2834 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2838 if (cl.time > p->time2)
2841 p->time2 = cl.time + (rand() & 3) * 0.1;
2842 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2843 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2845 a = CL_PointSuperContents(p->org);
2846 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2854 else if (p->delayedspawn > cl.time)
2858 // don't render particles too close to the view (they chew fillrate)
2859 // also don't render particles behind the view (useless)
2860 // further checks to cull to the frustum would be too slow here
2861 switch(p->typeindex)
2864 // beams have no culling
2865 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2868 if(cl_particles_visculling.integer)
2869 if (!r_refdef.viewcache.world_novis)
2870 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2872 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2874 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2877 // anything else just has to be in front of the viewer and visible at this distance
2878 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2879 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2886 if (cl.free_particle > i)
2887 cl.free_particle = i;
2890 // reduce cl.num_particles if possible
2891 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2894 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2896 particle_t *oldparticles = cl.particles;
2897 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2898 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2899 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2900 Mem_Free(oldparticles);