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)
483 dpsnprintf(filename, sizeof(filename), "maps/%s_effectinfo.txt", cl.levelname);
486 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
489 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
499 void CL_ReadPointFile_f (void);
500 void CL_Particles_Init (void)
502 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)");
503 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
505 Cvar_RegisterVariable (&cl_particles);
506 Cvar_RegisterVariable (&cl_particles_quality);
507 Cvar_RegisterVariable (&cl_particles_alpha);
508 Cvar_RegisterVariable (&cl_particles_size);
509 Cvar_RegisterVariable (&cl_particles_quake);
510 Cvar_RegisterVariable (&cl_particles_blood);
511 Cvar_RegisterVariable (&cl_particles_blood_alpha);
512 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
513 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
514 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
515 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
516 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
517 Cvar_RegisterVariable (&cl_particles_explosions_shell);
518 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
519 Cvar_RegisterVariable (&cl_particles_rain);
520 Cvar_RegisterVariable (&cl_particles_snow);
521 Cvar_RegisterVariable (&cl_particles_smoke);
522 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
523 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
524 Cvar_RegisterVariable (&cl_particles_sparks);
525 Cvar_RegisterVariable (&cl_particles_bubbles);
526 Cvar_RegisterVariable (&cl_particles_visculling);
527 Cvar_RegisterVariable (&cl_particles_collisions);
528 Cvar_RegisterVariable (&cl_decals);
529 Cvar_RegisterVariable (&cl_decals_visculling);
530 Cvar_RegisterVariable (&cl_decals_time);
531 Cvar_RegisterVariable (&cl_decals_fadetime);
532 Cvar_RegisterVariable (&cl_decals_newsystem);
533 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
534 Cvar_RegisterVariable (&cl_decals_models);
535 Cvar_RegisterVariable (&cl_decals_bias);
536 Cvar_RegisterVariable (&cl_decals_max);
539 void CL_Particles_Shutdown (void)
543 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
544 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
546 // list of all 26 parameters:
547 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
548 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
549 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
550 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
551 // palpha - opacity of particle as 0-255 (can be more than 255)
552 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
553 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
554 // pgravity - how much effect gravity has on the particle (0-1)
555 // 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
556 // px,py,pz - starting origin of particle
557 // pvx,pvy,pvz - starting velocity of particle
558 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
559 // blendmode - one of the PBLEND_ values
560 // orientation - one of the PARTICLE_ values
561 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
562 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
563 // stainalpha: opacity of the stain as factor for alpha
564 // stainsize: size of the stain as factor for palpha
565 // angle: base rotation of the particle geometry around its center normal
566 // spin: rotation speed of the particle geometry around its center normal
567 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)
572 if (!cl_particles.integer)
574 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
575 if (cl.free_particle >= cl.max_particles)
578 lifetime = palpha / min(1, palphafade);
579 part = &cl.particles[cl.free_particle++];
580 if (cl.num_particles < cl.free_particle)
581 cl.num_particles = cl.free_particle;
582 memset(part, 0, sizeof(*part));
583 VectorCopy(sortorigin, part->sortorigin);
584 part->typeindex = ptypeindex;
585 part->blendmode = blendmode;
586 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
588 particletexture_t *tex = &particletexture[ptex];
589 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
590 part->orientation = PARTICLE_VBEAM;
592 part->orientation = PARTICLE_HBEAM;
595 part->orientation = orientation;
596 l2 = (int)lhrandom(0.5, 256.5);
598 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
599 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
600 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
601 part->staintexnum = staintex;
602 if(staincolor1 >= 0 && staincolor2 >= 0)
604 l2 = (int)lhrandom(0.5, 256.5);
606 if(blendmode == PBLEND_INVMOD)
608 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
609 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
610 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
614 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
615 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
616 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
618 if(r > 0xFF) r = 0xFF;
619 if(g > 0xFF) g = 0xFF;
620 if(b > 0xFF) b = 0xFF;
624 r = part->color[0]; // -1 is shorthand for stain = particle color
628 part->staincolor[0] = r;
629 part->staincolor[1] = g;
630 part->staincolor[2] = b;
631 part->stainalpha = palpha * stainalpha / 256;
632 part->stainsize = psize * stainsize;
635 part->sizeincrease = psizeincrease;
636 part->alpha = palpha;
637 part->alphafade = palphafade;
638 part->gravity = pgravity;
639 part->bounce = pbounce;
640 part->stretch = stretch;
642 part->org[0] = px + originjitter * v[0];
643 part->org[1] = py + originjitter * v[1];
644 part->org[2] = pz + originjitter * v[2];
645 part->vel[0] = pvx + velocityjitter * v[0];
646 part->vel[1] = pvy + velocityjitter * v[1];
647 part->vel[2] = pvz + velocityjitter * v[2];
649 part->airfriction = pairfriction;
650 part->liquidfriction = pliquidfriction;
651 part->die = cl.time + lifetime;
652 // part->delayedcollisions = 0;
653 part->qualityreduction = pqualityreduction;
656 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
657 if (part->typeindex == pt_rain)
661 float lifetime = part->die - cl.time;
664 // turn raindrop into simple spark and create delayedspawn splash effect
665 part->typeindex = pt_spark;
667 VectorMA(part->org, lifetime, part->vel, endvec);
668 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
669 part->die = cl.time + lifetime * trace.fraction;
670 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);
673 part2->delayedspawn = part->die;
674 part2->die += part->die - cl.time;
675 for (i = rand() & 7;i < 10;i++)
677 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);
680 part2->delayedspawn = part->die;
681 part2->die += part->die - cl.time;
687 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
689 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
692 VectorMA(part->org, lifetime, part->vel, endvec);
693 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
694 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
701 static void CL_ImmediateBloodStain(particle_t *part)
706 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
707 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
709 VectorCopy(part->vel, v);
711 staintex = part->staintexnum;
712 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);
715 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
716 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
718 VectorCopy(part->vel, v);
720 staintex = tex_blooddecal[rand()&7];
721 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);
725 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
729 entity_render_t *ent = &cl.entities[hitent].render;
730 unsigned char color[3];
731 if (!cl_decals.integer)
733 if (!ent->allowdecals)
736 l2 = (int)lhrandom(0.5, 256.5);
738 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
739 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
740 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
742 if (cl_decals_newsystem.integer)
744 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);
748 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
749 if (cl.free_decal >= cl.max_decals)
751 decal = &cl.decals[cl.free_decal++];
752 if (cl.num_decals < cl.free_decal)
753 cl.num_decals = cl.free_decal;
754 memset(decal, 0, sizeof(*decal));
755 decal->decalsequence = cl.decalsequence++;
756 decal->typeindex = pt_decal;
757 decal->texnum = texnum;
758 VectorMA(org, cl_decals_bias.value, normal, decal->org);
759 VectorCopy(normal, decal->normal);
761 decal->alpha = alpha;
762 decal->time2 = cl.time;
763 decal->color[0] = color[0];
764 decal->color[1] = color[1];
765 decal->color[2] = color[2];
766 decal->owner = hitent;
767 decal->clusterindex = -1000; // no vis culling unless we're sure
770 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
771 decal->ownermodel = cl.entities[decal->owner].render.model;
772 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
773 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
777 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
779 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
781 decal->clusterindex = leaf->clusterindex;
786 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
789 float bestfrac, bestorg[3], bestnormal[3];
791 int besthitent = 0, hitent;
794 for (i = 0;i < 32;i++)
797 VectorMA(org, maxdist, org2, org2);
798 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
799 // take the closest trace result that doesn't end up hitting a NOMARKS
800 // surface (sky for example)
801 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
803 bestfrac = trace.fraction;
805 VectorCopy(trace.endpos, bestorg);
806 VectorCopy(trace.plane.normal, bestnormal);
810 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
813 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
814 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
815 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)
818 matrix4x4_t tempmatrix;
820 VectorLerp(originmins, 0.5, originmaxs, center);
821 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
822 if (effectnameindex == EFFECT_SVC_PARTICLE)
824 if (cl_particles.integer)
826 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
828 CL_ParticleExplosion(center);
829 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
830 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
833 count *= cl_particles_quality.value;
834 for (;count > 0;count--)
836 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
837 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);
842 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
843 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
844 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
845 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
846 else if (effectnameindex == EFFECT_TE_SPIKE)
848 if (cl_particles_bulletimpacts.integer)
850 if (cl_particles_quake.integer)
852 if (cl_particles_smoke.integer)
853 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
857 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
858 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
859 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);
863 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
864 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
866 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
868 if (cl_particles_bulletimpacts.integer)
870 if (cl_particles_quake.integer)
872 if (cl_particles_smoke.integer)
873 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
877 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
878 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
879 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);
883 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
884 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
885 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);
887 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
889 if (cl_particles_bulletimpacts.integer)
891 if (cl_particles_quake.integer)
893 if (cl_particles_smoke.integer)
894 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
898 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
899 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
900 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);
904 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
905 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
907 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
909 if (cl_particles_bulletimpacts.integer)
911 if (cl_particles_quake.integer)
913 if (cl_particles_smoke.integer)
914 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
918 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
919 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
920 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);
924 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
925 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
926 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);
928 else if (effectnameindex == EFFECT_TE_BLOOD)
930 if (!cl_particles_blood.integer)
932 if (cl_particles_quake.integer)
933 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
936 static double bloodaccumulator = 0;
937 qboolean immediatebloodstain = true;
938 //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);
939 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
940 for (;bloodaccumulator > 0;bloodaccumulator--)
942 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);
943 if (immediatebloodstain && part)
945 immediatebloodstain = false;
946 CL_ImmediateBloodStain(part);
951 else if (effectnameindex == EFFECT_TE_SPARK)
952 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
953 else if (effectnameindex == EFFECT_TE_PLASMABURN)
955 // plasma scorch mark
956 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
957 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
958 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
960 else if (effectnameindex == EFFECT_TE_GUNSHOT)
962 if (cl_particles_bulletimpacts.integer)
964 if (cl_particles_quake.integer)
965 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
968 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
969 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
970 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);
974 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
975 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
977 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
979 if (cl_particles_bulletimpacts.integer)
981 if (cl_particles_quake.integer)
982 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
985 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
986 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
987 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);
991 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
992 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
993 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);
995 else if (effectnameindex == EFFECT_TE_EXPLOSION)
997 CL_ParticleExplosion(center);
998 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);
1000 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1002 CL_ParticleExplosion(center);
1003 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);
1005 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1007 if (cl_particles_quake.integer)
1010 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1013 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);
1015 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);
1019 CL_ParticleExplosion(center);
1020 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);
1022 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1023 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);
1024 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1026 count *= cl_particles_quality.value;
1028 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);
1030 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1032 float i, j, inc, vel;
1035 inc = 8 / cl_particles_quality.value;
1036 for (i = -128;i < 128;i += inc)
1038 for (j = -128;j < 128;j += inc)
1040 dir[0] = j + lhrandom(0, inc);
1041 dir[1] = i + lhrandom(0, inc);
1043 org[0] = center[0] + dir[0];
1044 org[1] = center[1] + dir[1];
1045 org[2] = center[2] + lhrandom(0, 64);
1046 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1047 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);
1051 else if (effectnameindex == EFFECT_TE_TELEPORT)
1053 float i, j, k, inc, vel;
1056 if (cl_particles_quake.integer)
1057 inc = 4 / cl_particles_quality.value;
1059 inc = 8 / cl_particles_quality.value;
1060 for (i = -16;i < 16;i += inc)
1062 for (j = -16;j < 16;j += inc)
1064 for (k = -24;k < 32;k += inc)
1066 VectorSet(dir, i*8, j*8, k*8);
1067 VectorNormalize(dir);
1068 vel = lhrandom(50, 113);
1069 if (cl_particles_quake.integer)
1070 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);
1072 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);
1076 if (!cl_particles_quake.integer)
1077 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);
1078 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);
1080 else if (effectnameindex == EFFECT_TE_TEI_G3)
1081 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);
1082 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1084 if (cl_particles_smoke.integer)
1086 count *= 0.25f * cl_particles_quality.value;
1088 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);
1091 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1093 CL_ParticleExplosion(center);
1094 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);
1096 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1099 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1100 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1101 if (cl_particles_smoke.integer)
1102 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1103 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);
1104 if (cl_particles_sparks.integer)
1105 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1106 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);
1107 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);
1109 else if (effectnameindex == EFFECT_EF_FLAME)
1111 count *= 300 * cl_particles_quality.value;
1113 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);
1114 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);
1116 else if (effectnameindex == EFFECT_EF_STARDUST)
1118 count *= 200 * cl_particles_quality.value;
1120 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);
1121 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);
1123 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1127 int smoke, blood, bubbles, r, color;
1129 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1132 Vector4Set(light, 0, 0, 0, 0);
1134 if (effectnameindex == EFFECT_TR_ROCKET)
1135 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1136 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1138 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1139 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1141 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1143 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1144 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1148 matrix4x4_t tempmatrix;
1149 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1150 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);
1151 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1155 if (!spawnparticles)
1158 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1161 VectorSubtract(originmaxs, originmins, dir);
1162 len = VectorNormalizeLength(dir);
1165 dec = -ent->persistent.trail_time;
1166 ent->persistent.trail_time += len;
1167 if (ent->persistent.trail_time < 0.01f)
1170 // if we skip out, leave it reset
1171 ent->persistent.trail_time = 0.0f;
1176 // advance into this frame to reach the first puff location
1177 VectorMA(originmins, dec, dir, pos);
1180 smoke = cl_particles.integer && cl_particles_smoke.integer;
1181 blood = cl_particles.integer && cl_particles_blood.integer;
1182 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1183 qd = 1.0f / cl_particles_quality.value;
1190 if (effectnameindex == EFFECT_TR_BLOOD)
1192 if (cl_particles_quake.integer)
1194 color = particlepalette[67 + (rand()&3)];
1195 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);
1200 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);
1203 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1205 if (cl_particles_quake.integer)
1208 color = particlepalette[67 + (rand()&3)];
1209 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);
1214 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);
1220 if (effectnameindex == EFFECT_TR_ROCKET)
1222 if (cl_particles_quake.integer)
1225 color = particlepalette[ramp3[r]];
1226 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);
1230 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);
1231 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);
1234 else if (effectnameindex == EFFECT_TR_GRENADE)
1236 if (cl_particles_quake.integer)
1239 color = particlepalette[ramp3[r]];
1240 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);
1244 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);
1247 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1249 if (cl_particles_quake.integer)
1252 color = particlepalette[52 + (rand()&7)];
1253 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);
1254 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);
1256 else if (gamemode == GAME_GOODVSBAD2)
1259 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);
1263 color = particlepalette[20 + (rand()&7)];
1264 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);
1267 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1269 if (cl_particles_quake.integer)
1272 color = particlepalette[230 + (rand()&7)];
1273 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);
1274 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 color = particlepalette[226 + (rand()&7)];
1279 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);
1282 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1284 if (cl_particles_quake.integer)
1286 color = particlepalette[152 + (rand()&3)];
1287 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);
1289 else if (gamemode == GAME_GOODVSBAD2)
1292 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);
1294 else if (gamemode == GAME_PRYDON)
1297 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);
1300 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);
1302 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1305 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);
1307 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1310 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);
1312 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1313 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);
1317 if (effectnameindex == EFFECT_TR_ROCKET)
1318 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);
1319 else if (effectnameindex == EFFECT_TR_GRENADE)
1320 CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1322 // advance to next time and position
1325 VectorMA (pos, dec, dir, pos);
1328 ent->persistent.trail_time = len;
1331 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1334 // this is also called on point effects with spawndlight = true and
1335 // spawnparticles = true
1336 // it is called CL_ParticleTrail because most code does not want to supply
1337 // these parameters, only trail handling does
1338 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)
1340 qboolean found = false;
1341 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1343 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1344 return; // no such effect
1346 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1348 int effectinfoindex;
1351 particleeffectinfo_t *info;
1358 qboolean underwater;
1359 qboolean immediatebloodstain;
1361 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1362 VectorLerp(originmins, 0.5, originmaxs, center);
1363 supercontents = CL_PointSuperContents(center);
1364 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1365 VectorSubtract(originmaxs, originmins, traildir);
1366 traillen = VectorLength(traildir);
1367 VectorNormalize(traildir);
1368 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1370 if (info->effectnameindex == effectnameindex)
1373 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1375 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1378 // spawn a dlight if requested
1379 if (info->lightradiusstart > 0 && spawndlight)
1381 matrix4x4_t tempmatrix;
1382 if (info->trailspacing > 0)
1383 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1385 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1386 if (info->lighttime > 0 && info->lightradiusfade > 0)
1388 // light flash (explosion, etc)
1389 // called when effect starts
1390 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);
1392 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1395 // called by CL_LinkNetworkEntity
1396 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1397 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);
1398 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1402 if (!spawnparticles)
1407 if (info->tex[1] > info->tex[0])
1409 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1410 tex = min(tex, info->tex[1] - 1);
1412 if(info->staintex[0] < 0)
1413 staintex = info->staintex[0];
1416 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1417 staintex = min(staintex, info->staintex[1] - 1);
1419 if (info->particletype == pt_decal)
1420 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]);
1421 else if (info->orientation == PARTICLE_HBEAM)
1422 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);
1425 if (!cl_particles.integer)
1427 switch (info->particletype)
1429 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1430 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1431 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1432 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1433 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1434 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1437 VectorCopy(originmins, trailpos);
1438 if (info->trailspacing > 0)
1440 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1441 trailstep = info->trailspacing / cl_particles_quality.value;
1442 immediatebloodstain = false;
1446 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1448 immediatebloodstain = info->particletype == pt_blood || staintex;
1450 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1451 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1453 if (info->tex[1] > info->tex[0])
1455 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1456 tex = min(tex, info->tex[1] - 1);
1460 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1461 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1462 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1465 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]));
1466 if (immediatebloodstain && part)
1468 immediatebloodstain = false;
1469 CL_ImmediateBloodStain(part);
1472 VectorMA(trailpos, trailstep, traildir, trailpos);
1479 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1482 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)
1484 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1492 void CL_EntityParticles (const entity_t *ent)
1495 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1496 static vec3_t avelocities[NUMVERTEXNORMALS];
1497 if (!cl_particles.integer) return;
1498 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1500 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1502 if (!avelocities[0][0])
1503 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1504 avelocities[0][i] = lhrandom(0, 2.55);
1506 for (i = 0;i < NUMVERTEXNORMALS;i++)
1508 yaw = cl.time * avelocities[i][0];
1509 pitch = cl.time * avelocities[i][1];
1510 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1511 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1512 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1513 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);
1518 void CL_ReadPointFile_f (void)
1520 vec3_t org, leakorg;
1522 char *pointfile = NULL, *pointfilepos, *t, tchar;
1523 char name[MAX_OSPATH];
1528 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1529 strlcat (name, ".pts", sizeof (name));
1530 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1533 Con_Printf("Could not open %s\n", name);
1537 Con_Printf("Reading %s...\n", name);
1538 VectorClear(leakorg);
1541 pointfilepos = pointfile;
1542 while (*pointfilepos)
1544 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1549 while (*t && *t != '\n' && *t != '\r')
1553 #if _MSC_VER >= 1400
1554 #define sscanf sscanf_s
1556 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1562 VectorCopy(org, leakorg);
1565 if (cl.num_particles < cl.max_particles - 3)
1568 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);
1571 Mem_Free(pointfile);
1572 VectorCopy(leakorg, org);
1573 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1575 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);
1576 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);
1577 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);
1582 CL_ParseParticleEffect
1584 Parse an effect out of the server message
1587 void CL_ParseParticleEffect (void)
1590 int i, count, msgcount, color;
1592 MSG_ReadVector(org, cls.protocol);
1593 for (i=0 ; i<3 ; i++)
1594 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1595 msgcount = MSG_ReadByte ();
1596 color = MSG_ReadByte ();
1598 if (msgcount == 255)
1603 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1608 CL_ParticleExplosion
1612 void CL_ParticleExplosion (const vec3_t org)
1618 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1619 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1621 if (cl_particles_quake.integer)
1623 for (i = 0;i < 1024;i++)
1629 color = particlepalette[ramp1[r]];
1630 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);
1634 color = particlepalette[ramp2[r]];
1635 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);
1641 i = CL_PointSuperContents(org);
1642 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1644 if (cl_particles.integer && cl_particles_bubbles.integer)
1645 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1646 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);
1650 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1652 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1656 for (k = 0;k < 16;k++)
1659 VectorMA(org, 128, v2, v);
1660 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1661 if (trace.fraction >= 0.1)
1664 VectorSubtract(trace.endpos, org, v2);
1665 VectorScale(v2, 2.0f, v2);
1666 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);
1672 if (cl_particles_explosions_shell.integer)
1673 R_NewExplosion(org);
1678 CL_ParticleExplosion2
1682 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1685 if (!cl_particles.integer) return;
1687 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1689 k = particlepalette[colorStart + (i % colorLength)];
1690 if (cl_particles_quake.integer)
1691 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);
1693 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);
1697 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1700 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1701 if (cl_particles_sparks.integer)
1703 sparkcount *= cl_particles_quality.value;
1704 while(sparkcount-- > 0)
1705 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);
1709 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1712 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1713 if (cl_particles_smoke.integer)
1715 smokecount *= cl_particles_quality.value;
1716 while(smokecount-- > 0)
1717 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);
1721 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)
1725 if (!cl_particles.integer) return;
1726 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1728 count = (int)(count * cl_particles_quality.value);
1731 k = particlepalette[colorbase + (rand()&3)];
1732 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);
1736 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1739 float minz, maxz, lifetime = 30;
1741 if (!cl_particles.integer) return;
1742 if (dir[2] < 0) // falling
1744 minz = maxs[2] + dir[2] * 0.1;
1747 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1752 maxz = maxs[2] + dir[2] * 0.1;
1754 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1757 count = (int)(count * cl_particles_quality.value);
1762 if (!cl_particles_rain.integer) break;
1763 count *= 4; // ick, this should be in the mod or maps?
1767 k = particlepalette[colorbase + (rand()&3)];
1768 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1769 if (gamemode == GAME_GOODVSBAD2)
1770 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);
1772 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);
1776 if (!cl_particles_snow.integer) break;
1779 k = particlepalette[colorbase + (rand()&3)];
1780 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1781 if (gamemode == GAME_GOODVSBAD2)
1782 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);
1784 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);
1788 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1792 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1793 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1794 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1795 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1797 #define PARTICLETEXTURESIZE 64
1798 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1800 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1804 dz = 1 - (dx*dx+dy*dy);
1805 if (dz > 0) // it does hit the sphere
1809 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1810 VectorNormalize(normal);
1811 dot = DotProduct(normal, light);
1812 if (dot > 0.5) // interior reflection
1813 f += ((dot * 2) - 1);
1814 else if (dot < -0.5) // exterior reflection
1815 f += ((dot * -2) - 1);
1817 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1818 VectorNormalize(normal);
1819 dot = DotProduct(normal, light);
1820 if (dot > 0.5) // interior reflection
1821 f += ((dot * 2) - 1);
1822 else if (dot < -0.5) // exterior reflection
1823 f += ((dot * -2) - 1);
1825 f += 16; // just to give it a haze so you can see the outline
1826 f = bound(0, f, 255);
1827 return (unsigned char) f;
1833 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1834 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1836 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1837 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1838 *width = particlefontcellwidth;
1839 *height = particlefontcellheight;
1842 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1844 int basex, basey, w, h, y;
1845 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1846 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1847 Sys_Error("invalid particle texture size for autogenerating");
1848 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1849 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1852 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1855 float cx, cy, dx, dy, f, iradius;
1857 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1858 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1859 iradius = 1.0f / radius;
1860 alpha *= (1.0f / 255.0f);
1861 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1863 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1867 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1872 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1873 d[0] += (int)(f * (blue - d[0]));
1874 d[1] += (int)(f * (green - d[1]));
1875 d[2] += (int)(f * (red - d[2]));
1881 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1884 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1886 data[0] = bound(minb, data[0], maxb);
1887 data[1] = bound(ming, data[1], maxg);
1888 data[2] = bound(minr, data[2], maxr);
1892 void particletextureinvert(unsigned char *data)
1895 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1897 data[0] = 255 - data[0];
1898 data[1] = 255 - data[1];
1899 data[2] = 255 - data[2];
1903 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1904 static void R_InitBloodTextures (unsigned char *particletexturedata)
1907 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1908 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1911 for (i = 0;i < 8;i++)
1913 memset(data, 255, datasize);
1914 for (k = 0;k < 24;k++)
1915 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1916 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1917 particletextureinvert(data);
1918 setuptex(tex_bloodparticle[i], data, particletexturedata);
1922 for (i = 0;i < 8;i++)
1924 memset(data, 255, datasize);
1926 for (j = 1;j < 10;j++)
1927 for (k = min(j, m - 1);k < m;k++)
1928 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1929 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1930 particletextureinvert(data);
1931 setuptex(tex_blooddecal[i], data, particletexturedata);
1937 //uncomment this to make engine save out particle font to a tga file when run
1938 //#define DUMPPARTICLEFONT
1940 static void R_InitParticleTexture (void)
1942 int x, y, d, i, k, m;
1943 int basex, basey, w, h;
1944 float dx, dy, f, s1, t1, s2, t2;
1947 fs_offset_t filesize;
1948 char texturename[MAX_QPATH];
1950 // a note: decals need to modulate (multiply) the background color to
1951 // properly darken it (stain), and they need to be able to alpha fade,
1952 // this is a very difficult challenge because it means fading to white
1953 // (no change to background) rather than black (darkening everything
1954 // behind the whole decal polygon), and to accomplish this the texture is
1955 // inverted (dark red blood on white background becomes brilliant cyan
1956 // and white on black background) so we can alpha fade it to black, then
1957 // we invert it again during the blendfunc to make it work...
1959 #ifndef DUMPPARTICLEFONT
1960 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1963 particlefonttexture = decalskinframe->base;
1964 // TODO maybe allow custom grid size?
1965 particlefontwidth = image_width;
1966 particlefontheight = image_height;
1967 particlefontcellwidth = image_width / 8;
1968 particlefontcellheight = image_height / 8;
1969 particlefontcols = 8;
1970 particlefontrows = 8;
1975 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1976 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1977 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1978 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1979 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1981 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1982 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1983 particlefontcols = 8;
1984 particlefontrows = 8;
1986 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1989 for (i = 0;i < 8;i++)
1991 memset(data, 255, datasize);
1994 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1995 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1997 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1999 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2000 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2002 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2003 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2005 d = (int)(d * (1-(dx*dx+dy*dy)));
2006 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2007 d = bound(0, d, 255);
2008 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2015 setuptex(tex_smoke[i], data, particletexturedata);
2019 memset(data, 255, datasize);
2020 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2022 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2023 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2025 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2026 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2027 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2030 setuptex(tex_rainsplash, data, particletexturedata);
2033 memset(data, 255, datasize);
2034 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2036 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2037 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2039 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2040 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2041 d = bound(0, d, 255);
2042 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2045 setuptex(tex_particle, data, particletexturedata);
2048 memset(data, 255, datasize);
2049 light[0] = 1;light[1] = 1;light[2] = 1;
2050 VectorNormalize(light);
2051 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2053 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2054 // stretch upper half of bubble by +50% and shrink lower half by -50%
2055 // (this gives an elongated teardrop shape)
2057 dy = (dy - 0.5f) * 2.0f;
2059 dy = (dy - 0.5f) / 1.5f;
2060 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2062 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2063 // shrink bubble width to half
2065 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2068 setuptex(tex_raindrop, data, particletexturedata);
2071 memset(data, 255, datasize);
2072 light[0] = 1;light[1] = 1;light[2] = 1;
2073 VectorNormalize(light);
2074 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2076 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2077 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2079 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2080 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2083 setuptex(tex_bubble, data, particletexturedata);
2085 // Blood particles and blood decals
2086 R_InitBloodTextures (particletexturedata);
2089 for (i = 0;i < 8;i++)
2091 memset(data, 255, datasize);
2092 for (k = 0;k < 12;k++)
2093 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2094 for (k = 0;k < 3;k++)
2095 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2096 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2097 particletextureinvert(data);
2098 setuptex(tex_bulletdecal[i], data, particletexturedata);
2101 #ifdef DUMPPARTICLEFONT
2102 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2105 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2106 particlefonttexture = decalskinframe->base;
2108 Mem_Free(particletexturedata);
2113 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2115 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2116 particletexture[i].texture = particlefonttexture;
2117 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2118 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2119 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2120 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2123 #ifndef DUMPPARTICLEFONT
2124 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
2125 if (!particletexture[tex_beam].texture)
2128 unsigned char noise3[64][64], data2[64][16][4];
2130 fractalnoise(&noise3[0][0], 64, 4);
2132 for (y = 0;y < 64;y++)
2134 dy = (y - 0.5f*64) / (64*0.5f-1);
2135 for (x = 0;x < 16;x++)
2137 dx = (x - 0.5f*16) / (16*0.5f-2);
2138 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2139 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2140 data2[y][x][3] = 255;
2144 #ifdef DUMPPARTICLEFONT
2145 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2147 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2149 particletexture[tex_beam].s1 = 0;
2150 particletexture[tex_beam].t1 = 0;
2151 particletexture[tex_beam].s2 = 1;
2152 particletexture[tex_beam].t2 = 1;
2154 // now load an texcoord/texture override file
2155 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2162 if(!COM_ParseToken_Simple(&bufptr, true, false))
2164 if(!strcmp(com_token, "\n"))
2165 continue; // empty line
2166 i = atoi(com_token);
2174 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2176 s1 = atof(com_token);
2177 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2179 t1 = atof(com_token);
2180 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2182 s2 = atof(com_token);
2183 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2185 t2 = atof(com_token);
2186 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2187 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2188 strlcpy(texturename, com_token, sizeof(texturename));
2195 strlcpy(texturename, com_token, sizeof(texturename));
2198 if (!texturename[0])
2200 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2203 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2205 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2208 particletexture[i].texture = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR, false)->base;
2209 particletexture[i].s1 = s1;
2210 particletexture[i].t1 = t1;
2211 particletexture[i].s2 = s2;
2212 particletexture[i].t2 = t2;
2218 static void r_part_start(void)
2221 // generate particlepalette for convenience from the main one
2222 for (i = 0;i < 256;i++)
2223 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2224 particletexturepool = R_AllocTexturePool();
2225 R_InitParticleTexture ();
2226 CL_Particles_LoadEffectInfo();
2229 static void r_part_shutdown(void)
2231 R_FreeTexturePool(&particletexturepool);
2234 static void r_part_newmap(void)
2237 R_SkinFrame_MarkUsed(decalskinframe);
2238 CL_Particles_LoadEffectInfo();
2241 #define BATCHSIZE 256
2242 unsigned short particle_elements[BATCHSIZE*6];
2243 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2245 void R_Particles_Init (void)
2248 for (i = 0;i < BATCHSIZE;i++)
2250 particle_elements[i*6+0] = i*4+0;
2251 particle_elements[i*6+1] = i*4+1;
2252 particle_elements[i*6+2] = i*4+2;
2253 particle_elements[i*6+3] = i*4+0;
2254 particle_elements[i*6+4] = i*4+2;
2255 particle_elements[i*6+5] = i*4+3;
2258 Cvar_RegisterVariable(&r_drawparticles);
2259 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2260 Cvar_RegisterVariable(&r_drawdecals);
2261 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2262 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2265 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2267 int surfacelistindex;
2269 float *v3f, *t2f, *c4f;
2270 particletexture_t *tex;
2271 float right[3], up[3], size, ca;
2272 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2273 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2275 RSurf_ActiveWorldEntity();
2277 r_refdef.stats.drawndecals += numsurfaces;
2278 R_Mesh_ResetTextureState();
2279 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2280 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2281 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2282 GL_DepthMask(false);
2283 GL_DepthRange(0, 1);
2284 GL_PolygonOffset(0, 0);
2286 GL_CullFace(GL_NONE);
2288 // generate all the vertices at once
2289 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2291 d = cl.decals + surfacelist[surfacelistindex];
2294 c4f = particle_color4f + 16*surfacelistindex;
2295 ca = d->alpha * alphascale;
2296 // ensure alpha multiplier saturates properly
2297 if (ca > 1.0f / 256.0f)
2299 if (r_refdef.fogenabled)
2300 ca *= RSurf_FogVertex(d->org);
2301 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2302 Vector4Copy(c4f, c4f + 4);
2303 Vector4Copy(c4f, c4f + 8);
2304 Vector4Copy(c4f, c4f + 12);
2306 // calculate vertex positions
2307 size = d->size * cl_particles_size.value;
2308 VectorVectors(d->normal, right, up);
2309 VectorScale(right, size, right);
2310 VectorScale(up, size, up);
2311 v3f = particle_vertex3f + 12*surfacelistindex;
2312 v3f[ 0] = d->org[0] - right[0] - up[0];
2313 v3f[ 1] = d->org[1] - right[1] - up[1];
2314 v3f[ 2] = d->org[2] - right[2] - up[2];
2315 v3f[ 3] = d->org[0] - right[0] + up[0];
2316 v3f[ 4] = d->org[1] - right[1] + up[1];
2317 v3f[ 5] = d->org[2] - right[2] + up[2];
2318 v3f[ 6] = d->org[0] + right[0] + up[0];
2319 v3f[ 7] = d->org[1] + right[1] + up[1];
2320 v3f[ 8] = d->org[2] + right[2] + up[2];
2321 v3f[ 9] = d->org[0] + right[0] - up[0];
2322 v3f[10] = d->org[1] + right[1] - up[1];
2323 v3f[11] = d->org[2] + right[2] - up[2];
2325 // calculate texcoords
2326 tex = &particletexture[d->texnum];
2327 t2f = particle_texcoord2f + 8*surfacelistindex;
2328 t2f[0] = tex->s1;t2f[1] = tex->t2;
2329 t2f[2] = tex->s1;t2f[3] = tex->t1;
2330 t2f[4] = tex->s2;t2f[5] = tex->t1;
2331 t2f[6] = tex->s2;t2f[7] = tex->t2;
2334 // now render the decals all at once
2335 // (this assumes they all use one particle font texture!)
2336 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2337 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2338 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 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 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2440 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2441 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2442 GL_DepthMask(false);
2443 GL_DepthRange(0, 1);
2444 GL_PolygonOffset(0, 0);
2446 GL_AlphaTest(false);
2447 GL_CullFace(GL_NONE);
2449 spintime = r_refdef.scene.time;
2451 // first generate all the vertices at once
2452 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2454 p = cl.particles + surfacelist[surfacelistindex];
2456 blendmode = (pblend_t)p->blendmode;
2460 case PBLEND_INVALID:
2462 alpha = p->alpha * colormultiplier[3];
2463 // ensure alpha multiplier saturates properly
2466 // additive and modulate can just fade out in fog (this is correct)
2467 if (r_refdef.fogenabled)
2468 alpha *= RSurf_FogVertex(p->org);
2469 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2470 alpha *= 1.0f / 256.0f;
2471 c4f[0] = p->color[0] * alpha;
2472 c4f[1] = p->color[1] * alpha;
2473 c4f[2] = p->color[2] * alpha;
2477 alpha = p->alpha * colormultiplier[3];
2478 // ensure alpha multiplier saturates properly
2481 // additive and modulate can just fade out in fog (this is correct)
2482 if (r_refdef.fogenabled)
2483 alpha *= RSurf_FogVertex(p->org);
2484 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2485 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2486 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2487 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2491 c4f[0] = p->color[0] * colormultiplier[0];
2492 c4f[1] = p->color[1] * colormultiplier[1];
2493 c4f[2] = p->color[2] * colormultiplier[2];
2494 c4f[3] = p->alpha * colormultiplier[3];
2495 // note: lighting is not cheap!
2496 if (particletype[p->typeindex].lighting)
2498 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2499 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2500 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2501 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2503 // mix in the fog color
2504 if (r_refdef.fogenabled)
2506 fog = RSurf_FogVertex(p->org);
2508 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2509 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2510 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2514 // copy the color into the other three vertices
2515 Vector4Copy(c4f, c4f + 4);
2516 Vector4Copy(c4f, c4f + 8);
2517 Vector4Copy(c4f, c4f + 12);
2519 size = p->size * cl_particles_size.value;
2520 tex = &particletexture[p->texnum];
2521 switch(p->orientation)
2523 // case PARTICLE_INVALID:
2524 case PARTICLE_BILLBOARD:
2525 if (p->angle + p->spin)
2527 spinrad = (p->angle + p->spin * spintime) * (float)(M_PI / 180.0f);
2528 spinsin = sin(spinrad) * size;
2529 spincos = cos(spinrad) * size;
2530 spinm1 = -p->stretch * spincos;
2533 spinm4 = -p->stretch * spincos;
2534 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2535 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2539 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2540 VectorScale(r_refdef.view.up, size, up);
2543 v3f[ 0] = p->org[0] - right[0] - up[0];
2544 v3f[ 1] = p->org[1] - right[1] - up[1];
2545 v3f[ 2] = p->org[2] - right[2] - up[2];
2546 v3f[ 3] = p->org[0] - right[0] + up[0];
2547 v3f[ 4] = p->org[1] - right[1] + up[1];
2548 v3f[ 5] = p->org[2] - right[2] + up[2];
2549 v3f[ 6] = p->org[0] + right[0] + up[0];
2550 v3f[ 7] = p->org[1] + right[1] + up[1];
2551 v3f[ 8] = p->org[2] + right[2] + up[2];
2552 v3f[ 9] = p->org[0] + right[0] - up[0];
2553 v3f[10] = p->org[1] + right[1] - up[1];
2554 v3f[11] = p->org[2] + right[2] - up[2];
2555 t2f[0] = tex->s1;t2f[1] = tex->t2;
2556 t2f[2] = tex->s1;t2f[3] = tex->t1;
2557 t2f[4] = tex->s2;t2f[5] = tex->t1;
2558 t2f[6] = tex->s2;t2f[7] = tex->t2;
2560 case PARTICLE_ORIENTED_DOUBLESIDED:
2561 VectorVectors(p->vel, baseright, baseup);
2562 if (p->angle + p->spin)
2564 spinrad = (p->angle + p->spin * spintime) * (float)(M_PI / 180.0f);
2565 spinsin = sin(spinrad) * size;
2566 spincos = cos(spinrad) * size;
2567 spinm1 = p->stretch * spincos;
2570 spinm4 = p->stretch * spincos;
2571 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2572 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2576 VectorScale(baseright, size * p->stretch, right);
2577 VectorScale(baseup, size, up);
2579 v3f[ 0] = p->org[0] - right[0] - up[0];
2580 v3f[ 1] = p->org[1] - right[1] - up[1];
2581 v3f[ 2] = p->org[2] - right[2] - up[2];
2582 v3f[ 3] = p->org[0] - right[0] + up[0];
2583 v3f[ 4] = p->org[1] - right[1] + up[1];
2584 v3f[ 5] = p->org[2] - right[2] + up[2];
2585 v3f[ 6] = p->org[0] + right[0] + up[0];
2586 v3f[ 7] = p->org[1] + right[1] + up[1];
2587 v3f[ 8] = p->org[2] + right[2] + up[2];
2588 v3f[ 9] = p->org[0] + right[0] - up[0];
2589 v3f[10] = p->org[1] + right[1] - up[1];
2590 v3f[11] = p->org[2] + right[2] - up[2];
2591 t2f[0] = tex->s1;t2f[1] = tex->t2;
2592 t2f[2] = tex->s1;t2f[3] = tex->t1;
2593 t2f[4] = tex->s2;t2f[5] = tex->t1;
2594 t2f[6] = tex->s2;t2f[7] = tex->t2;
2596 case PARTICLE_SPARK:
2597 len = VectorLength(p->vel);
2598 VectorNormalize2(p->vel, up);
2599 lenfactor = p->stretch * 0.04 * len;
2600 if(lenfactor < size * 0.5)
2601 lenfactor = size * 0.5;
2602 VectorMA(p->org, -lenfactor, up, v);
2603 VectorMA(p->org, lenfactor, up, up2);
2604 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2605 t2f[0] = tex->s1;t2f[1] = tex->t2;
2606 t2f[2] = tex->s1;t2f[3] = tex->t1;
2607 t2f[4] = tex->s2;t2f[5] = tex->t1;
2608 t2f[6] = tex->s2;t2f[7] = tex->t2;
2610 case PARTICLE_VBEAM:
2611 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2612 VectorSubtract(p->vel, p->org, up);
2613 VectorNormalize(up);
2614 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2615 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2616 t2f[0] = tex->s2;t2f[1] = v[0];
2617 t2f[2] = tex->s1;t2f[3] = v[0];
2618 t2f[4] = tex->s1;t2f[5] = v[1];
2619 t2f[6] = tex->s2;t2f[7] = v[1];
2621 case PARTICLE_HBEAM:
2622 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2623 VectorSubtract(p->vel, p->org, up);
2624 VectorNormalize(up);
2625 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2626 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2627 t2f[0] = v[0];t2f[1] = tex->t1;
2628 t2f[2] = v[0];t2f[3] = tex->t2;
2629 t2f[4] = v[1];t2f[5] = tex->t2;
2630 t2f[6] = v[1];t2f[7] = tex->t1;
2635 // now render batches of particles based on blendmode and texture
2636 blendmode = PBLEND_INVALID;
2640 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2642 p = cl.particles + surfacelist[surfacelistindex];
2644 if (blendmode != p->blendmode)
2646 blendmode = (pblend_t)p->blendmode;
2650 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2652 case PBLEND_INVALID:
2654 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2657 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2661 if (texture != particletexture[p->texnum].texture)
2663 texture = particletexture[p->texnum].texture;
2664 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2667 // iterate until we find a change in settings
2668 batchstart = surfacelistindex++;
2669 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2671 p = cl.particles + surfacelist[surfacelistindex];
2672 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2676 batchcount = surfacelistindex - batchstart;
2677 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2681 void R_DrawParticles (void)
2684 int drawparticles = r_drawparticles.integer;
2685 float minparticledist;
2687 float gravity, frametime, f, dist, oldorg[3];
2693 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2694 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2696 // LordHavoc: early out conditions
2697 if (!cl.num_particles)
2700 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2701 gravity = frametime * cl.movevars_gravity;
2702 update = frametime > 0;
2703 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2704 drawdist2 = drawdist2*drawdist2;
2706 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2710 if (cl.free_particle > i)
2711 cl.free_particle = i;
2717 if (p->delayedspawn > cl.time)
2719 p->delayedspawn = 0;
2721 p->size += p->sizeincrease * frametime;
2722 p->alpha -= p->alphafade * frametime;
2724 if (p->alpha <= 0 || p->die <= cl.time)
2727 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2729 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2731 if (p->typeindex == pt_blood)
2732 p->size += frametime * 8;
2734 p->vel[2] -= p->gravity * gravity;
2735 f = 1.0f - min(p->liquidfriction * frametime, 1);
2736 VectorScale(p->vel, f, p->vel);
2740 p->vel[2] -= p->gravity * gravity;
2743 f = 1.0f - min(p->airfriction * frametime, 1);
2744 VectorScale(p->vel, f, p->vel);
2748 VectorCopy(p->org, oldorg);
2749 VectorMA(p->org, frametime, p->vel, p->org);
2750 // if (p->bounce && cl.time >= p->delayedcollisions)
2751 if (p->bounce && cl_particles_collisions.integer)
2753 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);
2754 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2755 // or if the trace hit something flagged as NOIMPACT
2756 // then remove the particle
2757 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2759 VectorCopy(trace.endpos, p->org);
2760 // react if the particle hit something
2761 if (trace.fraction < 1)
2763 VectorCopy(trace.endpos, p->org);
2765 if (p->staintexnum >= 0)
2767 // blood - splash on solid
2768 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2771 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2772 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2773 if (cl_decals.integer)
2775 // create a decal for the blood splat
2776 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2777 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2782 if (p->typeindex == pt_blood)
2784 // blood - splash on solid
2785 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2787 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2789 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)));
2790 if (cl_decals.integer)
2792 // create a decal for the blood splat
2793 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);
2798 else if (p->bounce < 0)
2800 // bounce -1 means remove on impact
2805 // anything else - bounce off solid
2806 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2807 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2808 if (DotProduct(p->vel, p->vel) < 0.03)
2809 VectorClear(p->vel);
2815 if (p->typeindex != pt_static)
2817 switch (p->typeindex)
2819 case pt_entityparticle:
2820 // particle that removes itself after one rendered frame
2827 a = CL_PointSuperContents(p->org);
2828 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2832 a = CL_PointSuperContents(p->org);
2833 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2837 a = CL_PointSuperContents(p->org);
2838 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2842 if (cl.time > p->time2)
2845 p->time2 = cl.time + (rand() & 3) * 0.1;
2846 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2847 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2849 a = CL_PointSuperContents(p->org);
2850 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2858 else if (p->delayedspawn)
2862 // don't render particles too close to the view (they chew fillrate)
2863 // also don't render particles behind the view (useless)
2864 // further checks to cull to the frustum would be too slow here
2865 switch(p->typeindex)
2868 // beams have no culling
2869 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2872 if(cl_particles_visculling.integer)
2873 if (!r_refdef.viewcache.world_novis)
2874 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2876 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2878 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2881 // anything else just has to be in front of the viewer and visible at this distance
2882 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2883 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2890 if (cl.free_particle > i)
2891 cl.free_particle = i;
2894 // reduce cl.num_particles if possible
2895 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2898 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2900 particle_t *oldparticles = cl.particles;
2901 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2902 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2903 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2904 Mem_Free(oldparticles);