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];
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)
235 particleeffectinfo_t *info = NULL;
236 const char *text = textstart;
238 effectinfoindex = -1;
239 for (linenumber = 1;;linenumber++)
242 for (arrayindex = 0;arrayindex < 16;arrayindex++)
243 argv[arrayindex][0] = 0;
246 if (!COM_ParseToken_Simple(&text, true, false))
248 if (!strcmp(com_token, "\n"))
252 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
258 #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;}
259 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
260 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
261 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
262 #define readfloat(var) checkparms(2);var = atof(argv[1])
263 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
264 if (!strcmp(argv[0], "effect"))
269 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
271 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
274 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
276 if (particleeffectname[effectnameindex][0])
278 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
283 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
287 // if we run out of names, abort
288 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
290 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
293 info = particleeffectinfo + effectinfoindex;
294 info->effectnameindex = effectnameindex;
295 info->particletype = pt_alphastatic;
296 info->blendmode = particletype[info->particletype].blendmode;
297 info->orientation = particletype[info->particletype].orientation;
298 info->tex[0] = tex_particle;
299 info->tex[1] = tex_particle;
300 info->color[0] = 0xFFFFFF;
301 info->color[1] = 0xFFFFFF;
305 info->alpha[1] = 256;
306 info->alpha[2] = 256;
307 info->time[0] = 9999;
308 info->time[1] = 9999;
309 VectorSet(info->lightcolor, 1, 1, 1);
310 info->lightshadow = true;
311 info->lighttime = 9999;
312 info->stretchfactor = 1;
313 info->staincolor[0] = (unsigned int)-1;
314 info->staincolor[1] = (unsigned int)-1;
315 info->staintex[0] = -1;
316 info->staintex[1] = -1;
317 info->stainalpha[0] = 1;
318 info->stainalpha[1] = 1;
319 info->stainsize[0] = 2;
320 info->stainsize[1] = 2;
322 info->rotate[1] = 360;
326 else if (info == NULL)
328 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
331 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
332 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
333 else if (!strcmp(argv[0], "type"))
336 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
337 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
338 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
339 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
340 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
341 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
342 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
343 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
344 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
345 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
346 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
347 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
348 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
349 info->blendmode = particletype[info->particletype].blendmode;
350 info->orientation = particletype[info->particletype].orientation;
352 else if (!strcmp(argv[0], "blend"))
355 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
356 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
357 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
358 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
360 else if (!strcmp(argv[0], "orientation"))
363 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
364 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
365 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
366 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
367 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
369 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
370 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
371 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
372 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
373 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
374 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
375 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
376 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
377 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
378 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
379 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
380 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
381 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
382 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
383 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
384 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
385 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
386 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
387 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
388 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
389 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
390 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
391 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
392 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
393 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
394 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
395 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
396 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
397 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
398 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; }
399 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
401 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
410 int CL_ParticleEffectIndexForName(const char *name)
413 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
414 if (!strcmp(particleeffectname[i], name))
419 const char *CL_ParticleEffectNameForIndex(int i)
421 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
423 return particleeffectname[i];
426 // MUST match effectnameindex_t in client.h
427 static const char *standardeffectnames[EFFECT_TOTAL] =
451 "TE_TEI_BIGEXPLOSION",
467 void CL_Particles_LoadEffectInfo(void)
471 unsigned char *filedata;
472 fs_offset_t filesize;
473 char filename[MAX_QPATH];
474 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
475 memset(particleeffectname, 0, sizeof(particleeffectname));
476 for (i = 0;i < EFFECT_TOTAL;i++)
477 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
478 for (filepass = 0;;filepass++)
481 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
482 else if (filepass == 1)
484 if (!cl.worldbasename[0])
486 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
490 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
493 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
503 void CL_ReadPointFile_f (void);
504 void CL_Particles_Init (void)
506 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)");
507 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
509 Cvar_RegisterVariable (&cl_particles);
510 Cvar_RegisterVariable (&cl_particles_quality);
511 Cvar_RegisterVariable (&cl_particles_alpha);
512 Cvar_RegisterVariable (&cl_particles_size);
513 Cvar_RegisterVariable (&cl_particles_quake);
514 Cvar_RegisterVariable (&cl_particles_blood);
515 Cvar_RegisterVariable (&cl_particles_blood_alpha);
516 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
517 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
518 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
519 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
520 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
521 Cvar_RegisterVariable (&cl_particles_explosions_shell);
522 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
523 Cvar_RegisterVariable (&cl_particles_rain);
524 Cvar_RegisterVariable (&cl_particles_snow);
525 Cvar_RegisterVariable (&cl_particles_smoke);
526 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
527 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
528 Cvar_RegisterVariable (&cl_particles_sparks);
529 Cvar_RegisterVariable (&cl_particles_bubbles);
530 Cvar_RegisterVariable (&cl_particles_visculling);
531 Cvar_RegisterVariable (&cl_particles_collisions);
532 Cvar_RegisterVariable (&cl_decals);
533 Cvar_RegisterVariable (&cl_decals_visculling);
534 Cvar_RegisterVariable (&cl_decals_time);
535 Cvar_RegisterVariable (&cl_decals_fadetime);
536 Cvar_RegisterVariable (&cl_decals_newsystem);
537 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
538 Cvar_RegisterVariable (&cl_decals_models);
539 Cvar_RegisterVariable (&cl_decals_bias);
540 Cvar_RegisterVariable (&cl_decals_max);
543 void CL_Particles_Shutdown (void)
547 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
548 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
550 // list of all 26 parameters:
551 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
552 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
553 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
554 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
555 // palpha - opacity of particle as 0-255 (can be more than 255)
556 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
557 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
558 // pgravity - how much effect gravity has on the particle (0-1)
559 // 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
560 // px,py,pz - starting origin of particle
561 // pvx,pvy,pvz - starting velocity of particle
562 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
563 // blendmode - one of the PBLEND_ values
564 // orientation - one of the PARTICLE_ values
565 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
566 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
567 // stainalpha: opacity of the stain as factor for alpha
568 // stainsize: size of the stain as factor for palpha
569 // angle: base rotation of the particle geometry around its center normal
570 // spin: rotation speed of the particle geometry around its center normal
571 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)
576 if (!cl_particles.integer)
578 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
579 if (cl.free_particle >= cl.max_particles)
582 lifetime = palpha / min(1, palphafade);
583 part = &cl.particles[cl.free_particle++];
584 if (cl.num_particles < cl.free_particle)
585 cl.num_particles = cl.free_particle;
586 memset(part, 0, sizeof(*part));
587 VectorCopy(sortorigin, part->sortorigin);
588 part->typeindex = ptypeindex;
589 part->blendmode = blendmode;
590 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
592 particletexture_t *tex = &particletexture[ptex];
593 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
594 part->orientation = PARTICLE_VBEAM;
596 part->orientation = PARTICLE_HBEAM;
599 part->orientation = orientation;
600 l2 = (int)lhrandom(0.5, 256.5);
602 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
603 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
604 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
605 part->staintexnum = staintex;
606 if(staincolor1 >= 0 && staincolor2 >= 0)
608 l2 = (int)lhrandom(0.5, 256.5);
610 if(blendmode == PBLEND_INVMOD)
612 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
613 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
614 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
618 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
619 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
620 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
622 if(r > 0xFF) r = 0xFF;
623 if(g > 0xFF) g = 0xFF;
624 if(b > 0xFF) b = 0xFF;
628 r = part->color[0]; // -1 is shorthand for stain = particle color
632 part->staincolor[0] = r;
633 part->staincolor[1] = g;
634 part->staincolor[2] = b;
635 part->stainalpha = palpha * stainalpha;
636 part->stainsize = psize * stainsize;
639 part->sizeincrease = psizeincrease;
640 part->alpha = palpha;
641 part->alphafade = palphafade;
642 part->gravity = pgravity;
643 part->bounce = pbounce;
644 part->stretch = stretch;
646 part->org[0] = px + originjitter * v[0];
647 part->org[1] = py + originjitter * v[1];
648 part->org[2] = pz + originjitter * v[2];
649 part->vel[0] = pvx + velocityjitter * v[0];
650 part->vel[1] = pvy + velocityjitter * v[1];
651 part->vel[2] = pvz + velocityjitter * v[2];
653 part->airfriction = pairfriction;
654 part->liquidfriction = pliquidfriction;
655 part->die = cl.time + lifetime;
656 // part->delayedcollisions = 0;
657 part->qualityreduction = pqualityreduction;
660 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
661 if (part->typeindex == pt_rain)
665 float lifetime = part->die - cl.time;
668 // turn raindrop into simple spark and create delayedspawn splash effect
669 part->typeindex = pt_spark;
671 VectorMA(part->org, lifetime, part->vel, endvec);
672 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
673 part->die = cl.time + lifetime * trace.fraction;
674 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);
677 part2->delayedspawn = part->die;
678 part2->die += part->die - cl.time;
679 for (i = rand() & 7;i < 10;i++)
681 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);
684 part2->delayedspawn = part->die;
685 part2->die += part->die - cl.time;
691 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
693 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
696 VectorMA(part->org, lifetime, part->vel, endvec);
697 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
698 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
705 static void CL_ImmediateBloodStain(particle_t *part)
710 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
711 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
713 VectorCopy(part->vel, v);
715 staintex = part->staintexnum;
716 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);
719 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
720 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
722 VectorCopy(part->vel, v);
724 staintex = tex_blooddecal[rand()&7];
725 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);
729 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
733 entity_render_t *ent = &cl.entities[hitent].render;
734 unsigned char color[3];
735 if (!cl_decals.integer)
737 if (!ent->allowdecals)
740 l2 = (int)lhrandom(0.5, 256.5);
742 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
743 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
744 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
746 if (cl_decals_newsystem.integer)
748 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);
752 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
753 if (cl.free_decal >= cl.max_decals)
755 decal = &cl.decals[cl.free_decal++];
756 if (cl.num_decals < cl.free_decal)
757 cl.num_decals = cl.free_decal;
758 memset(decal, 0, sizeof(*decal));
759 decal->decalsequence = cl.decalsequence++;
760 decal->typeindex = pt_decal;
761 decal->texnum = texnum;
762 VectorMA(org, cl_decals_bias.value, normal, decal->org);
763 VectorCopy(normal, decal->normal);
765 decal->alpha = alpha;
766 decal->time2 = cl.time;
767 decal->color[0] = color[0];
768 decal->color[1] = color[1];
769 decal->color[2] = color[2];
770 decal->owner = hitent;
771 decal->clusterindex = -1000; // no vis culling unless we're sure
774 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
775 decal->ownermodel = cl.entities[decal->owner].render.model;
776 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
777 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
781 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
783 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
785 decal->clusterindex = leaf->clusterindex;
790 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
793 float bestfrac, bestorg[3], bestnormal[3];
795 int besthitent = 0, hitent;
798 for (i = 0;i < 32;i++)
801 VectorMA(org, maxdist, org2, org2);
802 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
803 // take the closest trace result that doesn't end up hitting a NOMARKS
804 // surface (sky for example)
805 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
807 bestfrac = trace.fraction;
809 VectorCopy(trace.endpos, bestorg);
810 VectorCopy(trace.plane.normal, bestnormal);
814 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
817 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
818 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
819 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)
822 matrix4x4_t tempmatrix;
824 VectorLerp(originmins, 0.5, originmaxs, center);
825 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
826 if (effectnameindex == EFFECT_SVC_PARTICLE)
828 if (cl_particles.integer)
830 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
832 CL_ParticleExplosion(center);
833 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
834 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
837 count *= cl_particles_quality.value;
838 for (;count > 0;count--)
840 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
841 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);
846 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
847 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
848 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
849 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
850 else if (effectnameindex == EFFECT_TE_SPIKE)
852 if (cl_particles_bulletimpacts.integer)
854 if (cl_particles_quake.integer)
856 if (cl_particles_smoke.integer)
857 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
861 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
862 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
863 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);
867 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
868 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
870 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
872 if (cl_particles_bulletimpacts.integer)
874 if (cl_particles_quake.integer)
876 if (cl_particles_smoke.integer)
877 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
881 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
882 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
883 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);
887 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
888 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
889 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);
891 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
893 if (cl_particles_bulletimpacts.integer)
895 if (cl_particles_quake.integer)
897 if (cl_particles_smoke.integer)
898 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
902 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
903 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
904 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);
908 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
909 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
911 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
913 if (cl_particles_bulletimpacts.integer)
915 if (cl_particles_quake.integer)
917 if (cl_particles_smoke.integer)
918 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
922 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
923 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
924 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);
928 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
929 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
930 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);
932 else if (effectnameindex == EFFECT_TE_BLOOD)
934 if (!cl_particles_blood.integer)
936 if (cl_particles_quake.integer)
937 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
940 static double bloodaccumulator = 0;
941 qboolean immediatebloodstain = true;
942 //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);
943 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
944 for (;bloodaccumulator > 0;bloodaccumulator--)
946 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);
947 if (immediatebloodstain && part)
949 immediatebloodstain = false;
950 CL_ImmediateBloodStain(part);
955 else if (effectnameindex == EFFECT_TE_SPARK)
956 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
957 else if (effectnameindex == EFFECT_TE_PLASMABURN)
959 // plasma scorch mark
960 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
961 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
962 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
964 else if (effectnameindex == EFFECT_TE_GUNSHOT)
966 if (cl_particles_bulletimpacts.integer)
968 if (cl_particles_quake.integer)
969 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
972 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
973 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
974 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);
978 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
979 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
981 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
983 if (cl_particles_bulletimpacts.integer)
985 if (cl_particles_quake.integer)
986 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
989 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
990 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
991 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);
995 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
996 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
997 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);
999 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1001 CL_ParticleExplosion(center);
1002 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);
1004 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1006 CL_ParticleExplosion(center);
1007 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);
1009 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1011 if (cl_particles_quake.integer)
1014 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1017 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);
1019 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);
1023 CL_ParticleExplosion(center);
1024 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);
1026 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1027 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);
1028 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1030 count *= cl_particles_quality.value;
1032 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);
1034 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1036 float i, j, inc, vel;
1039 inc = 8 / cl_particles_quality.value;
1040 for (i = -128;i < 128;i += inc)
1042 for (j = -128;j < 128;j += inc)
1044 dir[0] = j + lhrandom(0, inc);
1045 dir[1] = i + lhrandom(0, inc);
1047 org[0] = center[0] + dir[0];
1048 org[1] = center[1] + dir[1];
1049 org[2] = center[2] + lhrandom(0, 64);
1050 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1051 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);
1055 else if (effectnameindex == EFFECT_TE_TELEPORT)
1057 float i, j, k, inc, vel;
1060 if (cl_particles_quake.integer)
1061 inc = 4 / cl_particles_quality.value;
1063 inc = 8 / cl_particles_quality.value;
1064 for (i = -16;i < 16;i += inc)
1066 for (j = -16;j < 16;j += inc)
1068 for (k = -24;k < 32;k += inc)
1070 VectorSet(dir, i*8, j*8, k*8);
1071 VectorNormalize(dir);
1072 vel = lhrandom(50, 113);
1073 if (cl_particles_quake.integer)
1074 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);
1076 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);
1080 if (!cl_particles_quake.integer)
1081 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);
1082 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);
1084 else if (effectnameindex == EFFECT_TE_TEI_G3)
1085 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);
1086 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1088 if (cl_particles_smoke.integer)
1090 count *= 0.25f * cl_particles_quality.value;
1092 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);
1095 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1097 CL_ParticleExplosion(center);
1098 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);
1100 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1103 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1104 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1105 if (cl_particles_smoke.integer)
1106 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1107 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);
1108 if (cl_particles_sparks.integer)
1109 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1110 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);
1111 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);
1113 else if (effectnameindex == EFFECT_EF_FLAME)
1115 count *= 300 * cl_particles_quality.value;
1117 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);
1118 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);
1120 else if (effectnameindex == EFFECT_EF_STARDUST)
1122 count *= 200 * cl_particles_quality.value;
1124 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);
1125 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);
1127 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1131 int smoke, blood, bubbles, r, color;
1133 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1136 Vector4Set(light, 0, 0, 0, 0);
1138 if (effectnameindex == EFFECT_TR_ROCKET)
1139 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1140 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1142 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1143 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1145 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1147 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1148 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1152 matrix4x4_t tempmatrix;
1153 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1154 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);
1155 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1159 if (!spawnparticles)
1162 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1165 VectorSubtract(originmaxs, originmins, dir);
1166 len = VectorNormalizeLength(dir);
1169 dec = -ent->persistent.trail_time;
1170 ent->persistent.trail_time += len;
1171 if (ent->persistent.trail_time < 0.01f)
1174 // if we skip out, leave it reset
1175 ent->persistent.trail_time = 0.0f;
1180 // advance into this frame to reach the first puff location
1181 VectorMA(originmins, dec, dir, pos);
1184 smoke = cl_particles.integer && cl_particles_smoke.integer;
1185 blood = cl_particles.integer && cl_particles_blood.integer;
1186 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1187 qd = 1.0f / cl_particles_quality.value;
1194 if (effectnameindex == EFFECT_TR_BLOOD)
1196 if (cl_particles_quake.integer)
1198 color = particlepalette[67 + (rand()&3)];
1199 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);
1204 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);
1207 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1209 if (cl_particles_quake.integer)
1212 color = particlepalette[67 + (rand()&3)];
1213 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);
1218 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);
1224 if (effectnameindex == EFFECT_TR_ROCKET)
1226 if (cl_particles_quake.integer)
1229 color = particlepalette[ramp3[r]];
1230 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);
1234 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);
1235 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);
1238 else if (effectnameindex == EFFECT_TR_GRENADE)
1240 if (cl_particles_quake.integer)
1243 color = particlepalette[ramp3[r]];
1244 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);
1248 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);
1251 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1253 if (cl_particles_quake.integer)
1256 color = particlepalette[52 + (rand()&7)];
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);
1258 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);
1260 else if (gamemode == GAME_GOODVSBAD2)
1263 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);
1267 color = particlepalette[20 + (rand()&7)];
1268 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);
1271 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1273 if (cl_particles_quake.integer)
1276 color = particlepalette[230 + (rand()&7)];
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);
1278 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);
1282 color = particlepalette[226 + (rand()&7)];
1283 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);
1286 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1288 if (cl_particles_quake.integer)
1290 color = particlepalette[152 + (rand()&3)];
1291 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);
1293 else if (gamemode == GAME_GOODVSBAD2)
1296 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);
1298 else if (gamemode == GAME_PRYDON)
1301 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);
1304 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);
1306 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1309 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);
1311 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1314 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);
1316 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1317 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);
1321 if (effectnameindex == EFFECT_TR_ROCKET)
1322 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);
1323 else if (effectnameindex == EFFECT_TR_GRENADE)
1324 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);
1326 // advance to next time and position
1329 VectorMA (pos, dec, dir, pos);
1332 ent->persistent.trail_time = len;
1335 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1338 // this is also called on point effects with spawndlight = true and
1339 // spawnparticles = true
1340 // it is called CL_ParticleTrail because most code does not want to supply
1341 // these parameters, only trail handling does
1342 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)
1344 qboolean found = false;
1345 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1347 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1348 return; // no such effect
1350 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1352 int effectinfoindex;
1355 particleeffectinfo_t *info;
1362 qboolean underwater;
1363 qboolean immediatebloodstain;
1365 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1366 VectorLerp(originmins, 0.5, originmaxs, center);
1367 supercontents = CL_PointSuperContents(center);
1368 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1369 VectorSubtract(originmaxs, originmins, traildir);
1370 traillen = VectorLength(traildir);
1371 VectorNormalize(traildir);
1372 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1374 if (info->effectnameindex == effectnameindex)
1377 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1379 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1382 // spawn a dlight if requested
1383 if (info->lightradiusstart > 0 && spawndlight)
1385 matrix4x4_t tempmatrix;
1386 if (info->trailspacing > 0)
1387 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1389 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1390 if (info->lighttime > 0 && info->lightradiusfade > 0)
1392 // light flash (explosion, etc)
1393 // called when effect starts
1394 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);
1396 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1399 // called by CL_LinkNetworkEntity
1400 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1401 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);
1402 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1406 if (!spawnparticles)
1411 if (info->tex[1] > info->tex[0])
1413 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1414 tex = min(tex, info->tex[1] - 1);
1416 if(info->staintex[0] < 0)
1417 staintex = info->staintex[0];
1420 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1421 staintex = min(staintex, info->staintex[1] - 1);
1423 if (info->particletype == pt_decal)
1424 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]);
1425 else if (info->orientation == PARTICLE_HBEAM)
1426 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);
1429 if (!cl_particles.integer)
1431 switch (info->particletype)
1433 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1434 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1435 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1436 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1437 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1438 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1441 VectorCopy(originmins, trailpos);
1442 if (info->trailspacing > 0)
1444 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1445 trailstep = info->trailspacing / cl_particles_quality.value;
1446 immediatebloodstain = false;
1450 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1452 immediatebloodstain = info->particletype == pt_blood || staintex;
1454 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1455 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1457 if (info->tex[1] > info->tex[0])
1459 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1460 tex = min(tex, info->tex[1] - 1);
1464 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1465 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1466 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1469 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]));
1470 if (immediatebloodstain && part)
1472 immediatebloodstain = false;
1473 CL_ImmediateBloodStain(part);
1476 VectorMA(trailpos, trailstep, traildir, trailpos);
1483 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1486 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)
1488 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1496 void CL_EntityParticles (const entity_t *ent)
1499 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1500 static vec3_t avelocities[NUMVERTEXNORMALS];
1501 if (!cl_particles.integer) return;
1502 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1504 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1506 if (!avelocities[0][0])
1507 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1508 avelocities[0][i] = lhrandom(0, 2.55);
1510 for (i = 0;i < NUMVERTEXNORMALS;i++)
1512 yaw = cl.time * avelocities[i][0];
1513 pitch = cl.time * avelocities[i][1];
1514 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1515 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1516 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1517 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);
1522 void CL_ReadPointFile_f (void)
1524 vec3_t org, leakorg;
1526 char *pointfile = NULL, *pointfilepos, *t, tchar;
1527 char name[MAX_QPATH];
1532 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1533 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1536 Con_Printf("Could not open %s\n", name);
1540 Con_Printf("Reading %s...\n", name);
1541 VectorClear(leakorg);
1544 pointfilepos = pointfile;
1545 while (*pointfilepos)
1547 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1552 while (*t && *t != '\n' && *t != '\r')
1556 #if _MSC_VER >= 1400
1557 #define sscanf sscanf_s
1559 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1565 VectorCopy(org, leakorg);
1568 if (cl.num_particles < cl.max_particles - 3)
1571 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);
1574 Mem_Free(pointfile);
1575 VectorCopy(leakorg, org);
1576 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1578 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);
1579 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);
1580 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);
1585 CL_ParseParticleEffect
1587 Parse an effect out of the server message
1590 void CL_ParseParticleEffect (void)
1593 int i, count, msgcount, color;
1595 MSG_ReadVector(org, cls.protocol);
1596 for (i=0 ; i<3 ; i++)
1597 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1598 msgcount = MSG_ReadByte ();
1599 color = MSG_ReadByte ();
1601 if (msgcount == 255)
1606 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1611 CL_ParticleExplosion
1615 void CL_ParticleExplosion (const vec3_t org)
1621 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1622 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1624 if (cl_particles_quake.integer)
1626 for (i = 0;i < 1024;i++)
1632 color = particlepalette[ramp1[r]];
1633 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);
1637 color = particlepalette[ramp2[r]];
1638 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);
1644 i = CL_PointSuperContents(org);
1645 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1647 if (cl_particles.integer && cl_particles_bubbles.integer)
1648 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1649 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);
1653 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1655 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1659 for (k = 0;k < 16;k++)
1662 VectorMA(org, 128, v2, v);
1663 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1664 if (trace.fraction >= 0.1)
1667 VectorSubtract(trace.endpos, org, v2);
1668 VectorScale(v2, 2.0f, v2);
1669 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);
1675 if (cl_particles_explosions_shell.integer)
1676 R_NewExplosion(org);
1681 CL_ParticleExplosion2
1685 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1688 if (!cl_particles.integer) return;
1690 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1692 k = particlepalette[colorStart + (i % colorLength)];
1693 if (cl_particles_quake.integer)
1694 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);
1696 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);
1700 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1703 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1704 if (cl_particles_sparks.integer)
1706 sparkcount *= cl_particles_quality.value;
1707 while(sparkcount-- > 0)
1708 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);
1712 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1715 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1716 if (cl_particles_smoke.integer)
1718 smokecount *= cl_particles_quality.value;
1719 while(smokecount-- > 0)
1720 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);
1724 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)
1728 if (!cl_particles.integer) return;
1729 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1731 count = (int)(count * cl_particles_quality.value);
1734 k = particlepalette[colorbase + (rand()&3)];
1735 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);
1739 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1742 float minz, maxz, lifetime = 30;
1744 if (!cl_particles.integer) return;
1745 if (dir[2] < 0) // falling
1747 minz = maxs[2] + dir[2] * 0.1;
1750 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1755 maxz = maxs[2] + dir[2] * 0.1;
1757 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1760 count = (int)(count * cl_particles_quality.value);
1765 if (!cl_particles_rain.integer) break;
1766 count *= 4; // ick, this should be in the mod or maps?
1770 k = particlepalette[colorbase + (rand()&3)];
1771 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1772 if (gamemode == GAME_GOODVSBAD2)
1773 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);
1775 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);
1779 if (!cl_particles_snow.integer) break;
1782 k = particlepalette[colorbase + (rand()&3)];
1783 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1784 if (gamemode == GAME_GOODVSBAD2)
1785 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);
1787 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);
1791 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1795 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1796 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1797 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1798 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1800 #define PARTICLETEXTURESIZE 64
1801 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1803 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1807 dz = 1 - (dx*dx+dy*dy);
1808 if (dz > 0) // it does hit the sphere
1812 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1813 VectorNormalize(normal);
1814 dot = DotProduct(normal, light);
1815 if (dot > 0.5) // interior reflection
1816 f += ((dot * 2) - 1);
1817 else if (dot < -0.5) // exterior reflection
1818 f += ((dot * -2) - 1);
1820 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1821 VectorNormalize(normal);
1822 dot = DotProduct(normal, light);
1823 if (dot > 0.5) // interior reflection
1824 f += ((dot * 2) - 1);
1825 else if (dot < -0.5) // exterior reflection
1826 f += ((dot * -2) - 1);
1828 f += 16; // just to give it a haze so you can see the outline
1829 f = bound(0, f, 255);
1830 return (unsigned char) f;
1836 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1837 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1839 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1840 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1841 *width = particlefontcellwidth;
1842 *height = particlefontcellheight;
1845 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1847 int basex, basey, w, h, y;
1848 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1849 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1850 Sys_Error("invalid particle texture size for autogenerating");
1851 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1852 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1855 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1858 float cx, cy, dx, dy, f, iradius;
1860 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1861 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1862 iradius = 1.0f / radius;
1863 alpha *= (1.0f / 255.0f);
1864 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1866 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1870 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1875 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1876 d[0] += (int)(f * (blue - d[0]));
1877 d[1] += (int)(f * (green - d[1]));
1878 d[2] += (int)(f * (red - d[2]));
1884 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1887 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1889 data[0] = bound(minb, data[0], maxb);
1890 data[1] = bound(ming, data[1], maxg);
1891 data[2] = bound(minr, data[2], maxr);
1895 void particletextureinvert(unsigned char *data)
1898 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1900 data[0] = 255 - data[0];
1901 data[1] = 255 - data[1];
1902 data[2] = 255 - data[2];
1906 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1907 static void R_InitBloodTextures (unsigned char *particletexturedata)
1910 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1911 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1914 for (i = 0;i < 8;i++)
1916 memset(data, 255, datasize);
1917 for (k = 0;k < 24;k++)
1918 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1919 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1920 particletextureinvert(data);
1921 setuptex(tex_bloodparticle[i], data, particletexturedata);
1925 for (i = 0;i < 8;i++)
1927 memset(data, 255, datasize);
1929 for (j = 1;j < 10;j++)
1930 for (k = min(j, m - 1);k < m;k++)
1931 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1932 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1933 particletextureinvert(data);
1934 setuptex(tex_blooddecal[i], data, particletexturedata);
1940 //uncomment this to make engine save out particle font to a tga file when run
1941 //#define DUMPPARTICLEFONT
1943 static void R_InitParticleTexture (void)
1945 int x, y, d, i, k, m;
1946 int basex, basey, w, h;
1947 float dx, dy, f, s1, t1, s2, t2;
1950 fs_offset_t filesize;
1951 char texturename[MAX_QPATH];
1953 // a note: decals need to modulate (multiply) the background color to
1954 // properly darken it (stain), and they need to be able to alpha fade,
1955 // this is a very difficult challenge because it means fading to white
1956 // (no change to background) rather than black (darkening everything
1957 // behind the whole decal polygon), and to accomplish this the texture is
1958 // inverted (dark red blood on white background becomes brilliant cyan
1959 // and white on black background) so we can alpha fade it to black, then
1960 // we invert it again during the blendfunc to make it work...
1962 #ifndef DUMPPARTICLEFONT
1963 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1966 particlefonttexture = decalskinframe->base;
1967 // TODO maybe allow custom grid size?
1968 particlefontwidth = image_width;
1969 particlefontheight = image_height;
1970 particlefontcellwidth = image_width / 8;
1971 particlefontcellheight = image_height / 8;
1972 particlefontcols = 8;
1973 particlefontrows = 8;
1978 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1979 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1980 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1981 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1982 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1984 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1985 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1986 particlefontcols = 8;
1987 particlefontrows = 8;
1989 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1992 for (i = 0;i < 8;i++)
1994 memset(data, 255, datasize);
1997 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1998 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2000 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2002 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2003 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2005 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2006 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2008 d = (int)(d * (1-(dx*dx+dy*dy)));
2009 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2010 d = bound(0, d, 255);
2011 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2018 setuptex(tex_smoke[i], data, particletexturedata);
2022 memset(data, 255, datasize);
2023 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2025 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2026 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2028 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2029 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2030 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2033 setuptex(tex_rainsplash, data, particletexturedata);
2036 memset(data, 255, datasize);
2037 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2039 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2040 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2042 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2043 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2044 d = bound(0, d, 255);
2045 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2048 setuptex(tex_particle, data, particletexturedata);
2051 memset(data, 255, datasize);
2052 light[0] = 1;light[1] = 1;light[2] = 1;
2053 VectorNormalize(light);
2054 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2056 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2057 // stretch upper half of bubble by +50% and shrink lower half by -50%
2058 // (this gives an elongated teardrop shape)
2060 dy = (dy - 0.5f) * 2.0f;
2062 dy = (dy - 0.5f) / 1.5f;
2063 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2065 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2066 // shrink bubble width to half
2068 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2071 setuptex(tex_raindrop, data, particletexturedata);
2074 memset(data, 255, datasize);
2075 light[0] = 1;light[1] = 1;light[2] = 1;
2076 VectorNormalize(light);
2077 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2079 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2080 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2082 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2083 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2086 setuptex(tex_bubble, data, particletexturedata);
2088 // Blood particles and blood decals
2089 R_InitBloodTextures (particletexturedata);
2092 for (i = 0;i < 8;i++)
2094 memset(data, 255, datasize);
2095 for (k = 0;k < 12;k++)
2096 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2097 for (k = 0;k < 3;k++)
2098 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2099 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2100 particletextureinvert(data);
2101 setuptex(tex_bulletdecal[i], data, particletexturedata);
2104 #ifdef DUMPPARTICLEFONT
2105 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2108 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2109 particlefonttexture = decalskinframe->base;
2111 Mem_Free(particletexturedata);
2116 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2118 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2119 particletexture[i].texture = particlefonttexture;
2120 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2121 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2122 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2123 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2126 #ifndef DUMPPARTICLEFONT
2127 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
2128 if (!particletexture[tex_beam].texture)
2131 unsigned char noise3[64][64], data2[64][16][4];
2133 fractalnoise(&noise3[0][0], 64, 4);
2135 for (y = 0;y < 64;y++)
2137 dy = (y - 0.5f*64) / (64*0.5f-1);
2138 for (x = 0;x < 16;x++)
2140 dx = (x - 0.5f*16) / (16*0.5f-2);
2141 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2142 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2143 data2[y][x][3] = 255;
2147 #ifdef DUMPPARTICLEFONT
2148 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2150 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2152 particletexture[tex_beam].s1 = 0;
2153 particletexture[tex_beam].t1 = 0;
2154 particletexture[tex_beam].s2 = 1;
2155 particletexture[tex_beam].t2 = 1;
2157 // now load an texcoord/texture override file
2158 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2165 if(!COM_ParseToken_Simple(&bufptr, true, false))
2167 if(!strcmp(com_token, "\n"))
2168 continue; // empty line
2169 i = atoi(com_token);
2177 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2179 s1 = atof(com_token);
2180 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2182 t1 = atof(com_token);
2183 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2185 s2 = atof(com_token);
2186 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2188 t2 = atof(com_token);
2189 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2190 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2191 strlcpy(texturename, com_token, sizeof(texturename));
2198 strlcpy(texturename, com_token, sizeof(texturename));
2201 if (!texturename[0])
2203 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2206 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2208 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2211 particletexture[i].texture = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR, false)->base;
2212 particletexture[i].s1 = s1;
2213 particletexture[i].t1 = t1;
2214 particletexture[i].s2 = s2;
2215 particletexture[i].t2 = t2;
2221 static void r_part_start(void)
2224 // generate particlepalette for convenience from the main one
2225 for (i = 0;i < 256;i++)
2226 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2227 particletexturepool = R_AllocTexturePool();
2228 R_InitParticleTexture ();
2229 CL_Particles_LoadEffectInfo();
2232 static void r_part_shutdown(void)
2234 R_FreeTexturePool(&particletexturepool);
2237 static void r_part_newmap(void)
2240 R_SkinFrame_MarkUsed(decalskinframe);
2241 CL_Particles_LoadEffectInfo();
2244 #define BATCHSIZE 256
2245 unsigned short particle_elements[BATCHSIZE*6];
2246 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2248 void R_Particles_Init (void)
2251 for (i = 0;i < BATCHSIZE;i++)
2253 particle_elements[i*6+0] = i*4+0;
2254 particle_elements[i*6+1] = i*4+1;
2255 particle_elements[i*6+2] = i*4+2;
2256 particle_elements[i*6+3] = i*4+0;
2257 particle_elements[i*6+4] = i*4+2;
2258 particle_elements[i*6+5] = i*4+3;
2261 Cvar_RegisterVariable(&r_drawparticles);
2262 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2263 Cvar_RegisterVariable(&r_drawdecals);
2264 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2265 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2268 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2270 int surfacelistindex;
2272 float *v3f, *t2f, *c4f;
2273 particletexture_t *tex;
2274 float right[3], up[3], size, ca;
2275 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2276 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2278 RSurf_ActiveWorldEntity();
2280 r_refdef.stats.drawndecals += numsurfaces;
2281 R_Mesh_ResetTextureState();
2282 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2283 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2284 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2285 GL_DepthMask(false);
2286 GL_DepthRange(0, 1);
2287 GL_PolygonOffset(0, 0);
2289 GL_CullFace(GL_NONE);
2291 // generate all the vertices at once
2292 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2294 d = cl.decals + surfacelist[surfacelistindex];
2297 c4f = particle_color4f + 16*surfacelistindex;
2298 ca = d->alpha * alphascale;
2299 // ensure alpha multiplier saturates properly
2300 if (ca > 1.0f / 256.0f)
2302 if (r_refdef.fogenabled)
2303 ca *= RSurf_FogVertex(d->org);
2304 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2305 Vector4Copy(c4f, c4f + 4);
2306 Vector4Copy(c4f, c4f + 8);
2307 Vector4Copy(c4f, c4f + 12);
2309 // calculate vertex positions
2310 size = d->size * cl_particles_size.value;
2311 VectorVectors(d->normal, right, up);
2312 VectorScale(right, size, right);
2313 VectorScale(up, size, up);
2314 v3f = particle_vertex3f + 12*surfacelistindex;
2315 v3f[ 0] = d->org[0] - right[0] - up[0];
2316 v3f[ 1] = d->org[1] - right[1] - up[1];
2317 v3f[ 2] = d->org[2] - right[2] - up[2];
2318 v3f[ 3] = d->org[0] - right[0] + up[0];
2319 v3f[ 4] = d->org[1] - right[1] + up[1];
2320 v3f[ 5] = d->org[2] - right[2] + up[2];
2321 v3f[ 6] = d->org[0] + right[0] + up[0];
2322 v3f[ 7] = d->org[1] + right[1] + up[1];
2323 v3f[ 8] = d->org[2] + right[2] + up[2];
2324 v3f[ 9] = d->org[0] + right[0] - up[0];
2325 v3f[10] = d->org[1] + right[1] - up[1];
2326 v3f[11] = d->org[2] + right[2] - up[2];
2328 // calculate texcoords
2329 tex = &particletexture[d->texnum];
2330 t2f = particle_texcoord2f + 8*surfacelistindex;
2331 t2f[0] = tex->s1;t2f[1] = tex->t2;
2332 t2f[2] = tex->s1;t2f[3] = tex->t1;
2333 t2f[4] = tex->s2;t2f[5] = tex->t1;
2334 t2f[6] = tex->s2;t2f[7] = tex->t2;
2337 // now render the decals all at once
2338 // (this assumes they all use one particle font texture!)
2339 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2340 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2341 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2344 void R_DrawDecals (void)
2347 int drawdecals = r_drawdecals.integer;
2352 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2354 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2355 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2357 // LordHavoc: early out conditions
2361 decalfade = frametime * 256 / cl_decals_fadetime.value;
2362 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2363 drawdist2 = drawdist2*drawdist2;
2365 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2367 if (!decal->typeindex)
2370 if (killsequence - decal->decalsequence > 0)
2373 if (cl.time > decal->time2 + cl_decals_time.value)
2375 decal->alpha -= decalfade;
2376 if (decal->alpha <= 0)
2382 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2384 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2385 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2391 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2397 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))
2398 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2401 decal->typeindex = 0;
2402 if (cl.free_decal > i)
2406 // reduce cl.num_decals if possible
2407 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2410 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2412 decal_t *olddecals = cl.decals;
2413 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2414 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2415 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2416 Mem_Free(olddecals);
2419 r_refdef.stats.totaldecals = cl.num_decals;
2422 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2424 int surfacelistindex;
2425 int batchstart, batchcount;
2426 const particle_t *p;
2428 rtexture_t *texture;
2429 float *v3f, *t2f, *c4f;
2430 particletexture_t *tex;
2431 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2432 float ambient[3], diffuse[3], diffusenormal[3];
2433 float spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2434 vec4_t colormultiplier;
2436 RSurf_ActiveWorldEntity();
2438 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));
2440 r_refdef.stats.particles += numsurfaces;
2441 R_Mesh_ResetTextureState();
2442 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2443 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2444 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2445 GL_DepthMask(false);
2446 GL_DepthRange(0, 1);
2447 GL_PolygonOffset(0, 0);
2449 GL_AlphaTest(false);
2450 GL_CullFace(GL_NONE);
2452 spintime = r_refdef.scene.time;
2454 // first generate all the vertices at once
2455 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2457 p = cl.particles + surfacelist[surfacelistindex];
2459 blendmode = (pblend_t)p->blendmode;
2463 case PBLEND_INVALID:
2465 alpha = p->alpha * colormultiplier[3];
2466 // ensure alpha multiplier saturates properly
2469 // additive and modulate can just fade out in fog (this is correct)
2470 if (r_refdef.fogenabled)
2471 alpha *= RSurf_FogVertex(p->org);
2472 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2473 alpha *= 1.0f / 256.0f;
2474 c4f[0] = p->color[0] * alpha;
2475 c4f[1] = p->color[1] * alpha;
2476 c4f[2] = p->color[2] * alpha;
2480 alpha = p->alpha * colormultiplier[3];
2481 // ensure alpha multiplier saturates properly
2484 // additive and modulate can just fade out in fog (this is correct)
2485 if (r_refdef.fogenabled)
2486 alpha *= RSurf_FogVertex(p->org);
2487 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2488 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2489 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2490 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2494 c4f[0] = p->color[0] * colormultiplier[0];
2495 c4f[1] = p->color[1] * colormultiplier[1];
2496 c4f[2] = p->color[2] * colormultiplier[2];
2497 c4f[3] = p->alpha * colormultiplier[3];
2498 // note: lighting is not cheap!
2499 if (particletype[p->typeindex].lighting)
2501 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2502 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2503 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2504 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2506 // mix in the fog color
2507 if (r_refdef.fogenabled)
2509 fog = RSurf_FogVertex(p->org);
2511 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2512 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2513 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2517 // copy the color into the other three vertices
2518 Vector4Copy(c4f, c4f + 4);
2519 Vector4Copy(c4f, c4f + 8);
2520 Vector4Copy(c4f, c4f + 12);
2522 size = p->size * cl_particles_size.value;
2523 tex = &particletexture[p->texnum];
2524 switch(p->orientation)
2526 // case PARTICLE_INVALID:
2527 case PARTICLE_BILLBOARD:
2528 if (p->angle + p->spin)
2530 spinrad = (p->angle + p->spin * spintime) * (float)(M_PI / 180.0f);
2531 spinsin = sin(spinrad) * size;
2532 spincos = cos(spinrad) * size;
2533 spinm1 = -p->stretch * spincos;
2536 spinm4 = -p->stretch * spincos;
2537 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2538 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2542 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2543 VectorScale(r_refdef.view.up, size, up);
2546 v3f[ 0] = p->org[0] - right[0] - up[0];
2547 v3f[ 1] = p->org[1] - right[1] - up[1];
2548 v3f[ 2] = p->org[2] - right[2] - up[2];
2549 v3f[ 3] = p->org[0] - right[0] + up[0];
2550 v3f[ 4] = p->org[1] - right[1] + up[1];
2551 v3f[ 5] = p->org[2] - right[2] + up[2];
2552 v3f[ 6] = p->org[0] + right[0] + up[0];
2553 v3f[ 7] = p->org[1] + right[1] + up[1];
2554 v3f[ 8] = p->org[2] + right[2] + up[2];
2555 v3f[ 9] = p->org[0] + right[0] - up[0];
2556 v3f[10] = p->org[1] + right[1] - up[1];
2557 v3f[11] = p->org[2] + right[2] - up[2];
2558 t2f[0] = tex->s1;t2f[1] = tex->t2;
2559 t2f[2] = tex->s1;t2f[3] = tex->t1;
2560 t2f[4] = tex->s2;t2f[5] = tex->t1;
2561 t2f[6] = tex->s2;t2f[7] = tex->t2;
2563 case PARTICLE_ORIENTED_DOUBLESIDED:
2564 VectorVectors(p->vel, baseright, baseup);
2565 if (p->angle + p->spin)
2567 spinrad = (p->angle + p->spin * spintime) * (float)(M_PI / 180.0f);
2568 spinsin = sin(spinrad) * size;
2569 spincos = cos(spinrad) * size;
2570 spinm1 = p->stretch * spincos;
2573 spinm4 = p->stretch * spincos;
2574 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2575 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2579 VectorScale(baseright, size * p->stretch, right);
2580 VectorScale(baseup, size, up);
2582 v3f[ 0] = p->org[0] - right[0] - up[0];
2583 v3f[ 1] = p->org[1] - right[1] - up[1];
2584 v3f[ 2] = p->org[2] - right[2] - up[2];
2585 v3f[ 3] = p->org[0] - right[0] + up[0];
2586 v3f[ 4] = p->org[1] - right[1] + up[1];
2587 v3f[ 5] = p->org[2] - right[2] + up[2];
2588 v3f[ 6] = p->org[0] + right[0] + up[0];
2589 v3f[ 7] = p->org[1] + right[1] + up[1];
2590 v3f[ 8] = p->org[2] + right[2] + up[2];
2591 v3f[ 9] = p->org[0] + right[0] - up[0];
2592 v3f[10] = p->org[1] + right[1] - up[1];
2593 v3f[11] = p->org[2] + right[2] - up[2];
2594 t2f[0] = tex->s1;t2f[1] = tex->t2;
2595 t2f[2] = tex->s1;t2f[3] = tex->t1;
2596 t2f[4] = tex->s2;t2f[5] = tex->t1;
2597 t2f[6] = tex->s2;t2f[7] = tex->t2;
2599 case PARTICLE_SPARK:
2600 len = VectorLength(p->vel);
2601 VectorNormalize2(p->vel, up);
2602 lenfactor = p->stretch * 0.04 * len;
2603 if(lenfactor < size * 0.5)
2604 lenfactor = size * 0.5;
2605 VectorMA(p->org, -lenfactor, up, v);
2606 VectorMA(p->org, lenfactor, up, up2);
2607 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2608 t2f[0] = tex->s1;t2f[1] = tex->t2;
2609 t2f[2] = tex->s1;t2f[3] = tex->t1;
2610 t2f[4] = tex->s2;t2f[5] = tex->t1;
2611 t2f[6] = tex->s2;t2f[7] = tex->t2;
2613 case PARTICLE_VBEAM:
2614 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2615 VectorSubtract(p->vel, p->org, up);
2616 VectorNormalize(up);
2617 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2618 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2619 t2f[0] = tex->s2;t2f[1] = v[0];
2620 t2f[2] = tex->s1;t2f[3] = v[0];
2621 t2f[4] = tex->s1;t2f[5] = v[1];
2622 t2f[6] = tex->s2;t2f[7] = v[1];
2624 case PARTICLE_HBEAM:
2625 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2626 VectorSubtract(p->vel, p->org, up);
2627 VectorNormalize(up);
2628 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2629 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2630 t2f[0] = v[0];t2f[1] = tex->t1;
2631 t2f[2] = v[0];t2f[3] = tex->t2;
2632 t2f[4] = v[1];t2f[5] = tex->t2;
2633 t2f[6] = v[1];t2f[7] = tex->t1;
2638 // now render batches of particles based on blendmode and texture
2639 blendmode = PBLEND_INVALID;
2643 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2645 p = cl.particles + surfacelist[surfacelistindex];
2647 if (blendmode != p->blendmode)
2649 blendmode = (pblend_t)p->blendmode;
2653 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2655 case PBLEND_INVALID:
2657 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2660 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2664 if (texture != particletexture[p->texnum].texture)
2666 texture = particletexture[p->texnum].texture;
2667 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2670 // iterate until we find a change in settings
2671 batchstart = surfacelistindex++;
2672 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2674 p = cl.particles + surfacelist[surfacelistindex];
2675 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2679 batchcount = surfacelistindex - batchstart;
2680 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2684 void R_DrawParticles (void)
2687 int drawparticles = r_drawparticles.integer;
2688 float minparticledist;
2690 float gravity, frametime, f, dist, oldorg[3];
2696 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2697 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2699 // LordHavoc: early out conditions
2700 if (!cl.num_particles)
2703 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2704 gravity = frametime * cl.movevars_gravity;
2705 update = frametime > 0;
2706 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2707 drawdist2 = drawdist2*drawdist2;
2709 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2713 if (cl.free_particle > i)
2714 cl.free_particle = i;
2720 if (p->delayedspawn > cl.time)
2722 p->delayedspawn = 0;
2724 p->size += p->sizeincrease * frametime;
2725 p->alpha -= p->alphafade * frametime;
2727 if (p->alpha <= 0 || p->die <= cl.time)
2730 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2732 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2734 if (p->typeindex == pt_blood)
2735 p->size += frametime * 8;
2737 p->vel[2] -= p->gravity * gravity;
2738 f = 1.0f - min(p->liquidfriction * frametime, 1);
2739 VectorScale(p->vel, f, p->vel);
2743 p->vel[2] -= p->gravity * gravity;
2746 f = 1.0f - min(p->airfriction * frametime, 1);
2747 VectorScale(p->vel, f, p->vel);
2751 VectorCopy(p->org, oldorg);
2752 VectorMA(p->org, frametime, p->vel, p->org);
2753 // if (p->bounce && cl.time >= p->delayedcollisions)
2754 if (p->bounce && cl_particles_collisions.integer)
2756 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);
2757 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2758 // or if the trace hit something flagged as NOIMPACT
2759 // then remove the particle
2760 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2762 VectorCopy(trace.endpos, p->org);
2763 // react if the particle hit something
2764 if (trace.fraction < 1)
2766 VectorCopy(trace.endpos, p->org);
2768 if (p->staintexnum >= 0)
2770 // blood - splash on solid
2771 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2774 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2775 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2776 if (cl_decals.integer)
2778 // create a decal for the blood splat
2779 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2780 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2785 if (p->typeindex == pt_blood)
2787 // blood - splash on solid
2788 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2790 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2792 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)));
2793 if (cl_decals.integer)
2795 // create a decal for the blood splat
2796 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);
2801 else if (p->bounce < 0)
2803 // bounce -1 means remove on impact
2808 // anything else - bounce off solid
2809 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2810 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2811 if (DotProduct(p->vel, p->vel) < 0.03)
2812 VectorClear(p->vel);
2818 if (p->typeindex != pt_static)
2820 switch (p->typeindex)
2822 case pt_entityparticle:
2823 // particle that removes itself after one rendered frame
2830 a = CL_PointSuperContents(p->org);
2831 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2835 a = CL_PointSuperContents(p->org);
2836 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2840 a = CL_PointSuperContents(p->org);
2841 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2845 if (cl.time > p->time2)
2848 p->time2 = cl.time + (rand() & 3) * 0.1;
2849 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2850 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2852 a = CL_PointSuperContents(p->org);
2853 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2861 else if (p->delayedspawn)
2865 // don't render particles too close to the view (they chew fillrate)
2866 // also don't render particles behind the view (useless)
2867 // further checks to cull to the frustum would be too slow here
2868 switch(p->typeindex)
2871 // beams have no culling
2872 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2875 if(cl_particles_visculling.integer)
2876 if (!r_refdef.viewcache.world_novis)
2877 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2879 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2881 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2884 // anything else just has to be in front of the viewer and visible at this distance
2885 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2886 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2893 if (cl.free_particle > i)
2894 cl.free_particle = i;
2897 // reduce cl.num_particles if possible
2898 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2901 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2903 particle_t *oldparticles = cl.particles;
2904 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2905 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2906 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2907 Mem_Free(oldparticles);