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;
2277 RSurf_ActiveWorldEntity();
2279 r_refdef.stats.drawndecals += numsurfaces;
2280 R_Mesh_ResetTextureState();
2281 GL_DepthMask(false);
2282 GL_DepthRange(0, 1);
2283 GL_PolygonOffset(0, 0);
2285 GL_CullFace(GL_NONE);
2287 // generate all the vertices at once
2288 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2290 d = cl.decals + surfacelist[surfacelistindex];
2293 c4f = particle_color4f + 16*surfacelistindex;
2294 ca = d->alpha * alphascale;
2295 // ensure alpha multiplier saturates properly
2296 if (ca > 1.0f / 256.0f)
2298 if (r_refdef.fogenabled)
2299 ca *= RSurf_FogVertex(d->org);
2300 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2301 Vector4Copy(c4f, c4f + 4);
2302 Vector4Copy(c4f, c4f + 8);
2303 Vector4Copy(c4f, c4f + 12);
2305 // calculate vertex positions
2306 size = d->size * cl_particles_size.value;
2307 VectorVectors(d->normal, right, up);
2308 VectorScale(right, size, right);
2309 VectorScale(up, size, up);
2310 v3f = particle_vertex3f + 12*surfacelistindex;
2311 v3f[ 0] = d->org[0] - right[0] - up[0];
2312 v3f[ 1] = d->org[1] - right[1] - up[1];
2313 v3f[ 2] = d->org[2] - right[2] - up[2];
2314 v3f[ 3] = d->org[0] - right[0] + up[0];
2315 v3f[ 4] = d->org[1] - right[1] + up[1];
2316 v3f[ 5] = d->org[2] - right[2] + up[2];
2317 v3f[ 6] = d->org[0] + right[0] + up[0];
2318 v3f[ 7] = d->org[1] + right[1] + up[1];
2319 v3f[ 8] = d->org[2] + right[2] + up[2];
2320 v3f[ 9] = d->org[0] + right[0] - up[0];
2321 v3f[10] = d->org[1] + right[1] - up[1];
2322 v3f[11] = d->org[2] + right[2] - up[2];
2324 // calculate texcoords
2325 tex = &particletexture[d->texnum];
2326 t2f = particle_texcoord2f + 8*surfacelistindex;
2327 t2f[0] = tex->s1;t2f[1] = tex->t2;
2328 t2f[2] = tex->s1;t2f[3] = tex->t1;
2329 t2f[4] = tex->s2;t2f[5] = tex->t1;
2330 t2f[6] = tex->s2;t2f[7] = tex->t2;
2333 // now render the decals all at once
2334 // (this assumes they all use one particle font texture!)
2335 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2336 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2337 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2338 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2341 void R_DrawDecals (void)
2344 int drawdecals = r_drawdecals.integer;
2349 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2351 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2352 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2354 // LordHavoc: early out conditions
2358 decalfade = frametime * 256 / cl_decals_fadetime.value;
2359 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2360 drawdist2 = drawdist2*drawdist2;
2362 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2364 if (!decal->typeindex)
2367 if (killsequence - decal->decalsequence > 0)
2370 if (cl.time > decal->time2 + cl_decals_time.value)
2372 decal->alpha -= decalfade;
2373 if (decal->alpha <= 0)
2379 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2381 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2382 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2388 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2394 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))
2395 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2398 decal->typeindex = 0;
2399 if (cl.free_decal > i)
2403 // reduce cl.num_decals if possible
2404 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2407 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2409 decal_t *olddecals = cl.decals;
2410 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2411 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2412 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2413 Mem_Free(olddecals);
2416 r_refdef.stats.totaldecals = cl.num_decals;
2419 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2421 int surfacelistindex;
2422 int batchstart, batchcount;
2423 const particle_t *p;
2425 rtexture_t *texture;
2426 float *v3f, *t2f, *c4f;
2427 particletexture_t *tex;
2428 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2429 float ambient[3], diffuse[3], diffusenormal[3];
2430 float spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2431 vec4_t colormultiplier;
2433 RSurf_ActiveWorldEntity();
2435 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));
2437 r_refdef.stats.particles += numsurfaces;
2438 R_Mesh_ResetTextureState();
2439 GL_DepthMask(false);
2440 GL_DepthRange(0, 1);
2441 GL_PolygonOffset(0, 0);
2443 GL_AlphaTest(false);
2444 GL_CullFace(GL_NONE);
2446 spintime = r_refdef.scene.time;
2448 // first generate all the vertices at once
2449 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2451 p = cl.particles + surfacelist[surfacelistindex];
2453 blendmode = (pblend_t)p->blendmode;
2457 case PBLEND_INVALID:
2459 alpha = p->alpha * colormultiplier[3];
2460 // ensure alpha multiplier saturates properly
2463 // additive and modulate can just fade out in fog (this is correct)
2464 if (r_refdef.fogenabled)
2465 alpha *= RSurf_FogVertex(p->org);
2466 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2467 alpha *= 1.0f / 256.0f;
2468 c4f[0] = p->color[0] * alpha;
2469 c4f[1] = p->color[1] * alpha;
2470 c4f[2] = p->color[2] * alpha;
2474 alpha = p->alpha * colormultiplier[3];
2475 // ensure alpha multiplier saturates properly
2478 // additive and modulate can just fade out in fog (this is correct)
2479 if (r_refdef.fogenabled)
2480 alpha *= RSurf_FogVertex(p->org);
2481 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2482 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2483 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2484 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2488 c4f[0] = p->color[0] * colormultiplier[0];
2489 c4f[1] = p->color[1] * colormultiplier[1];
2490 c4f[2] = p->color[2] * colormultiplier[2];
2491 c4f[3] = p->alpha * colormultiplier[3];
2492 // note: lighting is not cheap!
2493 if (particletype[p->typeindex].lighting)
2495 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2496 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2497 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2498 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2500 // mix in the fog color
2501 if (r_refdef.fogenabled)
2503 fog = RSurf_FogVertex(p->org);
2505 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2506 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2507 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2511 // copy the color into the other three vertices
2512 Vector4Copy(c4f, c4f + 4);
2513 Vector4Copy(c4f, c4f + 8);
2514 Vector4Copy(c4f, c4f + 12);
2516 size = p->size * cl_particles_size.value;
2517 tex = &particletexture[p->texnum];
2518 switch(p->orientation)
2520 // case PARTICLE_INVALID:
2521 case PARTICLE_BILLBOARD:
2522 if (p->angle + p->spin)
2524 spinrad = (p->angle + p->spin * spintime) * (float)(M_PI / 180.0f);
2525 spinsin = sin(spinrad) * size;
2526 spincos = cos(spinrad) * size;
2527 spinm1 = -p->stretch * spincos;
2530 spinm4 = -p->stretch * spincos;
2531 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2532 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2536 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2537 VectorScale(r_refdef.view.up, size, up);
2540 v3f[ 0] = p->org[0] - right[0] - up[0];
2541 v3f[ 1] = p->org[1] - right[1] - up[1];
2542 v3f[ 2] = p->org[2] - right[2] - up[2];
2543 v3f[ 3] = p->org[0] - right[0] + up[0];
2544 v3f[ 4] = p->org[1] - right[1] + up[1];
2545 v3f[ 5] = p->org[2] - right[2] + up[2];
2546 v3f[ 6] = p->org[0] + right[0] + up[0];
2547 v3f[ 7] = p->org[1] + right[1] + up[1];
2548 v3f[ 8] = p->org[2] + right[2] + up[2];
2549 v3f[ 9] = p->org[0] + right[0] - up[0];
2550 v3f[10] = p->org[1] + right[1] - up[1];
2551 v3f[11] = p->org[2] + right[2] - up[2];
2552 t2f[0] = tex->s1;t2f[1] = tex->t2;
2553 t2f[2] = tex->s1;t2f[3] = tex->t1;
2554 t2f[4] = tex->s2;t2f[5] = tex->t1;
2555 t2f[6] = tex->s2;t2f[7] = tex->t2;
2557 case PARTICLE_ORIENTED_DOUBLESIDED:
2558 VectorVectors(p->vel, baseright, baseup);
2559 if (p->angle + p->spin)
2561 spinrad = (p->angle + p->spin * spintime) * (float)(M_PI / 180.0f);
2562 spinsin = sin(spinrad) * size;
2563 spincos = cos(spinrad) * size;
2564 spinm1 = p->stretch * spincos;
2567 spinm4 = p->stretch * spincos;
2568 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2569 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2573 VectorScale(baseright, size * p->stretch, right);
2574 VectorScale(baseup, size, up);
2576 v3f[ 0] = p->org[0] - right[0] - up[0];
2577 v3f[ 1] = p->org[1] - right[1] - up[1];
2578 v3f[ 2] = p->org[2] - right[2] - up[2];
2579 v3f[ 3] = p->org[0] - right[0] + up[0];
2580 v3f[ 4] = p->org[1] - right[1] + up[1];
2581 v3f[ 5] = p->org[2] - right[2] + up[2];
2582 v3f[ 6] = p->org[0] + right[0] + up[0];
2583 v3f[ 7] = p->org[1] + right[1] + up[1];
2584 v3f[ 8] = p->org[2] + right[2] + up[2];
2585 v3f[ 9] = p->org[0] + right[0] - up[0];
2586 v3f[10] = p->org[1] + right[1] - up[1];
2587 v3f[11] = p->org[2] + right[2] - up[2];
2588 t2f[0] = tex->s1;t2f[1] = tex->t2;
2589 t2f[2] = tex->s1;t2f[3] = tex->t1;
2590 t2f[4] = tex->s2;t2f[5] = tex->t1;
2591 t2f[6] = tex->s2;t2f[7] = tex->t2;
2593 case PARTICLE_SPARK:
2594 len = VectorLength(p->vel);
2595 VectorNormalize2(p->vel, up);
2596 lenfactor = p->stretch * 0.04 * len;
2597 if(lenfactor < size * 0.5)
2598 lenfactor = size * 0.5;
2599 VectorMA(p->org, -lenfactor, up, v);
2600 VectorMA(p->org, lenfactor, up, up2);
2601 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2602 t2f[0] = tex->s1;t2f[1] = tex->t2;
2603 t2f[2] = tex->s1;t2f[3] = tex->t1;
2604 t2f[4] = tex->s2;t2f[5] = tex->t1;
2605 t2f[6] = tex->s2;t2f[7] = tex->t2;
2607 case PARTICLE_VBEAM:
2608 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2609 VectorSubtract(p->vel, p->org, up);
2610 VectorNormalize(up);
2611 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2612 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2613 t2f[0] = tex->s2;t2f[1] = v[0];
2614 t2f[2] = tex->s1;t2f[3] = v[0];
2615 t2f[4] = tex->s1;t2f[5] = v[1];
2616 t2f[6] = tex->s2;t2f[7] = v[1];
2618 case PARTICLE_HBEAM:
2619 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2620 VectorSubtract(p->vel, p->org, up);
2621 VectorNormalize(up);
2622 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2623 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2624 t2f[0] = v[0];t2f[1] = tex->t1;
2625 t2f[2] = v[0];t2f[3] = tex->t2;
2626 t2f[4] = v[1];t2f[5] = tex->t2;
2627 t2f[6] = v[1];t2f[7] = tex->t1;
2632 // now render batches of particles based on blendmode and texture
2633 blendmode = PBLEND_INVALID;
2637 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2638 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2640 p = cl.particles + surfacelist[surfacelistindex];
2642 if (blendmode != p->blendmode)
2644 blendmode = (pblend_t)p->blendmode;
2648 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2650 case PBLEND_INVALID:
2652 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2655 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2659 if (texture != particletexture[p->texnum].texture)
2661 texture = particletexture[p->texnum].texture;
2662 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2665 // iterate until we find a change in settings
2666 batchstart = surfacelistindex++;
2667 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2669 p = cl.particles + surfacelist[surfacelistindex];
2670 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2674 batchcount = surfacelistindex - batchstart;
2675 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2679 void R_DrawParticles (void)
2682 int drawparticles = r_drawparticles.integer;
2683 float minparticledist;
2685 float gravity, frametime, f, dist, oldorg[3];
2691 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2692 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2694 // LordHavoc: early out conditions
2695 if (!cl.num_particles)
2698 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2699 gravity = frametime * cl.movevars_gravity;
2700 update = frametime > 0;
2701 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2702 drawdist2 = drawdist2*drawdist2;
2704 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2708 if (cl.free_particle > i)
2709 cl.free_particle = i;
2715 if (p->delayedspawn > cl.time)
2717 p->delayedspawn = 0;
2719 p->size += p->sizeincrease * frametime;
2720 p->alpha -= p->alphafade * frametime;
2722 if (p->alpha <= 0 || p->die <= cl.time)
2725 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2727 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2729 if (p->typeindex == pt_blood)
2730 p->size += frametime * 8;
2732 p->vel[2] -= p->gravity * gravity;
2733 f = 1.0f - min(p->liquidfriction * frametime, 1);
2734 VectorScale(p->vel, f, p->vel);
2738 p->vel[2] -= p->gravity * gravity;
2741 f = 1.0f - min(p->airfriction * frametime, 1);
2742 VectorScale(p->vel, f, p->vel);
2746 VectorCopy(p->org, oldorg);
2747 VectorMA(p->org, frametime, p->vel, p->org);
2748 // if (p->bounce && cl.time >= p->delayedcollisions)
2749 if (p->bounce && cl_particles_collisions.integer)
2751 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);
2752 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2753 // or if the trace hit something flagged as NOIMPACT
2754 // then remove the particle
2755 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2757 VectorCopy(trace.endpos, p->org);
2758 // react if the particle hit something
2759 if (trace.fraction < 1)
2761 VectorCopy(trace.endpos, p->org);
2763 if (p->staintexnum >= 0)
2765 // blood - splash on solid
2766 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2769 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2770 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2771 if (cl_decals.integer)
2773 // create a decal for the blood splat
2774 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2775 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2780 if (p->typeindex == pt_blood)
2782 // blood - splash on solid
2783 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2785 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2787 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)));
2788 if (cl_decals.integer)
2790 // create a decal for the blood splat
2791 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);
2796 else if (p->bounce < 0)
2798 // bounce -1 means remove on impact
2803 // anything else - bounce off solid
2804 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2805 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2806 if (DotProduct(p->vel, p->vel) < 0.03)
2807 VectorClear(p->vel);
2813 if (p->typeindex != pt_static)
2815 switch (p->typeindex)
2817 case pt_entityparticle:
2818 // particle that removes itself after one rendered frame
2825 a = CL_PointSuperContents(p->org);
2826 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2830 a = CL_PointSuperContents(p->org);
2831 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2835 a = CL_PointSuperContents(p->org);
2836 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2840 if (cl.time > p->time2)
2843 p->time2 = cl.time + (rand() & 3) * 0.1;
2844 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2845 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2847 a = CL_PointSuperContents(p->org);
2848 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2856 else if (p->delayedspawn)
2860 // don't render particles too close to the view (they chew fillrate)
2861 // also don't render particles behind the view (useless)
2862 // further checks to cull to the frustum would be too slow here
2863 switch(p->typeindex)
2866 // beams have no culling
2867 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2870 if(cl_particles_visculling.integer)
2871 if (!r_refdef.viewcache.world_novis)
2872 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2874 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2876 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2879 // anything else just has to be in front of the viewer and visible at this distance
2880 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2881 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2888 if (cl.free_particle > i)
2889 cl.free_particle = i;
2892 // reduce cl.num_particles if possible
2893 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2896 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2898 particle_t *oldparticles = cl.particles;
2899 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2900 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2901 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2902 Mem_Free(oldparticles);