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 particleeffectinfo_t;
122 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
125 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
127 static int particlepalette[256];
129 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
130 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
131 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
132 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
133 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
134 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
135 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
136 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
137 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
138 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
139 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
140 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
141 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
142 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
143 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
144 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
145 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
146 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
147 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
148 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
149 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
150 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
151 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
152 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
153 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
154 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
155 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
156 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
157 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
158 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
159 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
160 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
163 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
164 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
165 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
167 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
169 // particletexture_t is a rectangle in the particlefonttexture
170 typedef struct particletexture_s
173 float s1, t1, s2, t2;
177 static rtexturepool_t *particletexturepool;
178 static rtexture_t *particlefonttexture;
179 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
180 skinframe_t *decalskinframe;
182 // texture numbers in particle font
183 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
184 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
185 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
186 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
187 static const int tex_rainsplash = 32;
188 static const int tex_particle = 63;
189 static const int tex_bubble = 62;
190 static const int tex_raindrop = 61;
191 static const int tex_beam = 60;
193 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
194 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
195 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
196 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
197 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
198 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
199 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
200 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
201 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
202 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
203 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
204 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
205 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
206 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
207 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
208 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
209 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
210 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
211 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
212 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
213 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
214 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
215 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
216 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
217 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
218 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
219 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "0", "enables new advanced decal system"};
220 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)"};
221 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
222 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
223 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
226 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
232 particleeffectinfo_t *info = NULL;
233 const char *text = textstart;
235 effectinfoindex = -1;
236 for (linenumber = 1;;linenumber++)
239 for (arrayindex = 0;arrayindex < 16;arrayindex++)
240 argv[arrayindex][0] = 0;
243 if (!COM_ParseToken_Simple(&text, true, false))
245 if (!strcmp(com_token, "\n"))
249 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
255 #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;}
256 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
257 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
258 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
259 #define readfloat(var) checkparms(2);var = atof(argv[1])
260 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
261 if (!strcmp(argv[0], "effect"))
266 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
268 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
271 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
273 if (particleeffectname[effectnameindex][0])
275 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
280 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
284 // if we run out of names, abort
285 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
287 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
290 info = particleeffectinfo + effectinfoindex;
291 info->effectnameindex = effectnameindex;
292 info->particletype = pt_alphastatic;
293 info->blendmode = particletype[info->particletype].blendmode;
294 info->orientation = particletype[info->particletype].orientation;
295 info->tex[0] = tex_particle;
296 info->tex[1] = tex_particle;
297 info->color[0] = 0xFFFFFF;
298 info->color[1] = 0xFFFFFF;
302 info->alpha[1] = 256;
303 info->alpha[2] = 256;
304 info->time[0] = 9999;
305 info->time[1] = 9999;
306 VectorSet(info->lightcolor, 1, 1, 1);
307 info->lightshadow = true;
308 info->lighttime = 9999;
309 info->stretchfactor = 1;
310 info->staincolor[0] = (unsigned int)-1;
311 info->staincolor[1] = (unsigned int)-1;
312 info->staintex[0] = -1;
313 info->staintex[1] = -1;
314 info->stainalpha[0] = 1;
315 info->stainalpha[1] = 1;
316 info->stainsize[0] = 2;
317 info->stainsize[1] = 2;
319 else if (info == NULL)
321 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
324 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
325 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
326 else if (!strcmp(argv[0], "type"))
329 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
330 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
331 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
332 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
333 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
334 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
335 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
336 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
337 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
338 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
339 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
340 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
341 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
342 info->blendmode = particletype[info->particletype].blendmode;
343 info->orientation = particletype[info->particletype].orientation;
345 else if (!strcmp(argv[0], "blend"))
348 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
349 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
350 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
351 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
353 else if (!strcmp(argv[0], "orientation"))
356 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
357 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
358 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
359 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
360 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
362 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
363 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
364 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
365 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
366 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
367 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
368 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
369 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
370 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
371 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
372 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
373 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
374 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
375 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
376 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
377 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
378 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
379 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
380 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
381 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
382 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
383 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
384 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
385 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
386 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
387 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
388 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
389 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
390 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
391 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; }
393 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
402 int CL_ParticleEffectIndexForName(const char *name)
405 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
406 if (!strcmp(particleeffectname[i], name))
411 const char *CL_ParticleEffectNameForIndex(int i)
413 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
415 return particleeffectname[i];
418 // MUST match effectnameindex_t in client.h
419 static const char *standardeffectnames[EFFECT_TOTAL] =
443 "TE_TEI_BIGEXPLOSION",
459 void CL_Particles_LoadEffectInfo(void)
463 unsigned char *filedata;
464 fs_offset_t filesize;
465 char filename[MAX_QPATH];
466 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
467 memset(particleeffectname, 0, sizeof(particleeffectname));
468 for (i = 0;i < EFFECT_TOTAL;i++)
469 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
470 for (filepass = 0;;filepass++)
473 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
474 else if (filepass == 1)
475 dpsnprintf(filename, sizeof(filename), "maps/%s_effectinfo.txt", cl.levelname);
478 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
481 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
491 void CL_ReadPointFile_f (void);
492 void CL_Particles_Init (void)
494 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)");
495 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
497 Cvar_RegisterVariable (&cl_particles);
498 Cvar_RegisterVariable (&cl_particles_quality);
499 Cvar_RegisterVariable (&cl_particles_alpha);
500 Cvar_RegisterVariable (&cl_particles_size);
501 Cvar_RegisterVariable (&cl_particles_quake);
502 Cvar_RegisterVariable (&cl_particles_blood);
503 Cvar_RegisterVariable (&cl_particles_blood_alpha);
504 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
505 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
506 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
507 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
508 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
509 Cvar_RegisterVariable (&cl_particles_explosions_shell);
510 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
511 Cvar_RegisterVariable (&cl_particles_rain);
512 Cvar_RegisterVariable (&cl_particles_snow);
513 Cvar_RegisterVariable (&cl_particles_smoke);
514 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
515 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
516 Cvar_RegisterVariable (&cl_particles_sparks);
517 Cvar_RegisterVariable (&cl_particles_bubbles);
518 Cvar_RegisterVariable (&cl_particles_visculling);
519 Cvar_RegisterVariable (&cl_decals);
520 Cvar_RegisterVariable (&cl_decals_visculling);
521 Cvar_RegisterVariable (&cl_decals_time);
522 Cvar_RegisterVariable (&cl_decals_fadetime);
523 Cvar_RegisterVariable (&cl_decals_newsystem);
524 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
525 Cvar_RegisterVariable (&cl_decals_models);
526 Cvar_RegisterVariable (&cl_decals_bias);
527 Cvar_RegisterVariable (&cl_decals_max);
530 void CL_Particles_Shutdown (void)
534 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
535 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
537 // list of all 26 parameters:
538 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
539 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
540 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
541 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
542 // palpha - opacity of particle as 0-255 (can be more than 255)
543 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
544 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
545 // pgravity - how much effect gravity has on the particle (0-1)
546 // 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
547 // px,py,pz - starting origin of particle
548 // pvx,pvy,pvz - starting velocity of particle
549 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
550 // blendmode - one of the PBLEND_ values
551 // orientation - one of the PARTICLE_ values
552 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
553 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
554 // stainalpha: opacity of the stain as factor for alpha
555 // stainsize: size of the stain as factor for palpha
556 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)
561 if (!cl_particles.integer)
563 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
564 if (cl.free_particle >= cl.max_particles)
567 lifetime = palpha / min(1, palphafade);
568 part = &cl.particles[cl.free_particle++];
569 if (cl.num_particles < cl.free_particle)
570 cl.num_particles = cl.free_particle;
571 memset(part, 0, sizeof(*part));
572 VectorCopy(sortorigin, part->sortorigin);
573 part->typeindex = ptypeindex;
574 part->blendmode = blendmode;
575 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
577 particletexture_t *tex = &particletexture[ptex];
578 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
579 part->orientation = PARTICLE_VBEAM;
581 part->orientation = PARTICLE_HBEAM;
584 part->orientation = orientation;
585 l2 = (int)lhrandom(0.5, 256.5);
587 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
588 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
589 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
590 part->staintexnum = staintex;
591 if(staincolor1 >= 0 && staincolor2 >= 0)
593 l2 = (int)lhrandom(0.5, 256.5);
595 if(blendmode == PBLEND_INVMOD)
597 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
598 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
599 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
603 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
604 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
605 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
607 if(r > 0xFF) r = 0xFF;
608 if(g > 0xFF) g = 0xFF;
609 if(b > 0xFF) b = 0xFF;
613 r = part->color[0]; // -1 is shorthand for stain = particle color
617 part->staincolor = r * 65536 + g * 256 + b;
618 part->stainalpha = palpha * stainalpha / 256;
619 part->stainsize = psize * stainsize;
622 part->sizeincrease = psizeincrease;
623 part->alpha = palpha;
624 part->alphafade = palphafade;
625 part->gravity = pgravity;
626 part->bounce = pbounce;
627 part->stretch = stretch;
629 part->org[0] = px + originjitter * v[0];
630 part->org[1] = py + originjitter * v[1];
631 part->org[2] = pz + originjitter * v[2];
632 part->vel[0] = pvx + velocityjitter * v[0];
633 part->vel[1] = pvy + velocityjitter * v[1];
634 part->vel[2] = pvz + velocityjitter * v[2];
636 part->airfriction = pairfriction;
637 part->liquidfriction = pliquidfriction;
638 part->die = cl.time + lifetime;
639 part->delayedcollisions = 0;
640 part->qualityreduction = pqualityreduction;
641 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
642 if (part->typeindex == pt_rain)
646 float lifetime = part->die - cl.time;
649 // turn raindrop into simple spark and create delayedspawn splash effect
650 part->typeindex = pt_spark;
652 VectorMA(part->org, lifetime, part->vel, endvec);
653 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
654 part->die = cl.time + lifetime * trace.fraction;
655 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);
658 part2->delayedspawn = part->die;
659 part2->die += part->die - cl.time;
660 for (i = rand() & 7;i < 10;i++)
662 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);
665 part2->delayedspawn = part->die;
666 part2->die += part->die - cl.time;
671 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
673 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
676 VectorMA(part->org, lifetime, part->vel, endvec);
677 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
678 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
684 static void CL_ImmediateBloodStain(particle_t *part)
689 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
690 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
692 VectorCopy(part->vel, v);
694 staintex = part->staintexnum;
695 R_DecalSystem_SplatEntities(part->org, v, 1-((part->staincolor>>16)&255)*(1.0f/255.0f), 1-((part->staincolor>>8)&255)*(1.0f/255.0f), 1-((part->staincolor)&255)*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize);
698 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
699 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
701 VectorCopy(part->vel, v);
703 staintex = tex_blooddecal[rand()&7];
704 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);
708 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
712 entity_render_t *ent = &cl.entities[hitent].render;
713 unsigned char color[3];
714 if (!cl_decals.integer)
716 if (!ent->allowdecals)
719 l2 = (int)lhrandom(0.5, 256.5);
721 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
722 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
723 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
725 if (cl_decals_newsystem.integer)
727 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);
731 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
732 if (cl.free_decal >= cl.max_decals)
734 decal = &cl.decals[cl.free_decal++];
735 if (cl.num_decals < cl.free_decal)
736 cl.num_decals = cl.free_decal;
737 memset(decal, 0, sizeof(*decal));
738 decal->decalsequence = cl.decalsequence++;
739 decal->typeindex = pt_decal;
740 decal->texnum = texnum;
741 VectorMA(org, cl_decals_bias.value, normal, decal->org);
742 VectorCopy(normal, decal->normal);
744 decal->alpha = alpha;
745 decal->time2 = cl.time;
746 decal->color[0] = color[0];
747 decal->color[1] = color[1];
748 decal->color[2] = color[2];
749 decal->owner = hitent;
750 decal->clusterindex = -1000; // no vis culling unless we're sure
753 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
754 decal->ownermodel = cl.entities[decal->owner].render.model;
755 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
756 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
760 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
762 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
764 decal->clusterindex = leaf->clusterindex;
769 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
772 float bestfrac, bestorg[3], bestnormal[3];
774 int besthitent = 0, hitent;
777 for (i = 0;i < 32;i++)
780 VectorMA(org, maxdist, org2, org2);
781 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
782 // take the closest trace result that doesn't end up hitting a NOMARKS
783 // surface (sky for example)
784 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
786 bestfrac = trace.fraction;
788 VectorCopy(trace.endpos, bestorg);
789 VectorCopy(trace.plane.normal, bestnormal);
793 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
796 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
797 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
798 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)
801 matrix4x4_t tempmatrix;
803 VectorLerp(originmins, 0.5, originmaxs, center);
804 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
805 if (effectnameindex == EFFECT_SVC_PARTICLE)
807 if (cl_particles.integer)
809 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
811 CL_ParticleExplosion(center);
812 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
813 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
816 count *= cl_particles_quality.value;
817 for (;count > 0;count--)
819 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
820 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);
825 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
826 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
827 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
828 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
829 else if (effectnameindex == EFFECT_TE_SPIKE)
831 if (cl_particles_bulletimpacts.integer)
833 if (cl_particles_quake.integer)
835 if (cl_particles_smoke.integer)
836 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
840 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
841 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
842 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);
846 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
847 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
849 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
851 if (cl_particles_bulletimpacts.integer)
853 if (cl_particles_quake.integer)
855 if (cl_particles_smoke.integer)
856 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
860 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
861 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
862 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1);
866 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
867 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
868 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);
870 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
872 if (cl_particles_bulletimpacts.integer)
874 if (cl_particles_quake.integer)
876 if (cl_particles_smoke.integer)
877 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
881 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
882 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
883 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1);
887 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
888 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
890 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
892 if (cl_particles_bulletimpacts.integer)
894 if (cl_particles_quake.integer)
896 if (cl_particles_smoke.integer)
897 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
901 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
902 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
903 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1);
907 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
908 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
909 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);
911 else if (effectnameindex == EFFECT_TE_BLOOD)
913 if (!cl_particles_blood.integer)
915 if (cl_particles_quake.integer)
916 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
919 static double bloodaccumulator = 0;
920 qboolean immediatebloodstain = true;
921 //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);
922 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
923 for (;bloodaccumulator > 0;bloodaccumulator--)
925 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);
926 if (immediatebloodstain && part)
928 immediatebloodstain = false;
929 CL_ImmediateBloodStain(part);
934 else if (effectnameindex == EFFECT_TE_SPARK)
935 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
936 else if (effectnameindex == EFFECT_TE_PLASMABURN)
938 // plasma scorch mark
939 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
940 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
941 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
943 else if (effectnameindex == EFFECT_TE_GUNSHOT)
945 if (cl_particles_bulletimpacts.integer)
947 if (cl_particles_quake.integer)
948 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
951 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
952 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
953 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);
957 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
958 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
960 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
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);
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);
976 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);
978 else if (effectnameindex == EFFECT_TE_EXPLOSION)
980 CL_ParticleExplosion(center);
981 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);
983 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
985 CL_ParticleExplosion(center);
986 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);
988 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
990 if (cl_particles_quake.integer)
993 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
996 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);
998 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);
1002 CL_ParticleExplosion(center);
1003 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);
1005 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1006 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);
1007 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1009 count *= cl_particles_quality.value;
1011 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);
1013 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1015 float i, j, inc, vel;
1018 inc = 8 / cl_particles_quality.value;
1019 for (i = -128;i < 128;i += inc)
1021 for (j = -128;j < 128;j += inc)
1023 dir[0] = j + lhrandom(0, inc);
1024 dir[1] = i + lhrandom(0, inc);
1026 org[0] = center[0] + dir[0];
1027 org[1] = center[1] + dir[1];
1028 org[2] = center[2] + lhrandom(0, 64);
1029 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1030 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);
1034 else if (effectnameindex == EFFECT_TE_TELEPORT)
1036 float i, j, k, inc, vel;
1039 if (cl_particles_quake.integer)
1040 inc = 4 / cl_particles_quality.value;
1042 inc = 8 / cl_particles_quality.value;
1043 for (i = -16;i < 16;i += inc)
1045 for (j = -16;j < 16;j += inc)
1047 for (k = -24;k < 32;k += inc)
1049 VectorSet(dir, i*8, j*8, k*8);
1050 VectorNormalize(dir);
1051 vel = lhrandom(50, 113);
1052 if (cl_particles_quake.integer)
1053 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);
1055 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);
1059 if (!cl_particles_quake.integer)
1060 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);
1061 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);
1063 else if (effectnameindex == EFFECT_TE_TEI_G3)
1064 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);
1065 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1067 if (cl_particles_smoke.integer)
1069 count *= 0.25f * cl_particles_quality.value;
1071 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);
1074 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1076 CL_ParticleExplosion(center);
1077 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);
1079 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1082 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1083 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1084 if (cl_particles_smoke.integer)
1085 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1086 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);
1087 if (cl_particles_sparks.integer)
1088 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1089 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);
1090 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);
1092 else if (effectnameindex == EFFECT_EF_FLAME)
1094 count *= 300 * cl_particles_quality.value;
1096 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);
1097 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);
1099 else if (effectnameindex == EFFECT_EF_STARDUST)
1101 count *= 200 * cl_particles_quality.value;
1103 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);
1104 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);
1106 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1110 int smoke, blood, bubbles, r, color;
1112 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1115 Vector4Set(light, 0, 0, 0, 0);
1117 if (effectnameindex == EFFECT_TR_ROCKET)
1118 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1119 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1121 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1122 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1124 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1126 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1127 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1131 matrix4x4_t tempmatrix;
1132 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1133 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);
1134 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1138 if (!spawnparticles)
1141 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1144 VectorSubtract(originmaxs, originmins, dir);
1145 len = VectorNormalizeLength(dir);
1148 dec = -ent->persistent.trail_time;
1149 ent->persistent.trail_time += len;
1150 if (ent->persistent.trail_time < 0.01f)
1153 // if we skip out, leave it reset
1154 ent->persistent.trail_time = 0.0f;
1159 // advance into this frame to reach the first puff location
1160 VectorMA(originmins, dec, dir, pos);
1163 smoke = cl_particles.integer && cl_particles_smoke.integer;
1164 blood = cl_particles.integer && cl_particles_blood.integer;
1165 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1166 qd = 1.0f / cl_particles_quality.value;
1173 if (effectnameindex == EFFECT_TR_BLOOD)
1175 if (cl_particles_quake.integer)
1177 color = particlepalette[67 + (rand()&3)];
1178 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);
1183 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);
1186 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1188 if (cl_particles_quake.integer)
1191 color = particlepalette[67 + (rand()&3)];
1192 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);
1197 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);
1203 if (effectnameindex == EFFECT_TR_ROCKET)
1205 if (cl_particles_quake.integer)
1208 color = particlepalette[ramp3[r]];
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, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1);
1213 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);
1214 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);
1217 else if (effectnameindex == EFFECT_TR_GRENADE)
1219 if (cl_particles_quake.integer)
1222 color = particlepalette[ramp3[r]];
1223 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);
1227 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);
1230 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1232 if (cl_particles_quake.integer)
1235 color = particlepalette[52 + (rand()&7)];
1236 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);
1237 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);
1239 else if (gamemode == GAME_GOODVSBAD2)
1242 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);
1246 color = particlepalette[20 + (rand()&7)];
1247 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);
1250 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1252 if (cl_particles_quake.integer)
1255 color = particlepalette[230 + (rand()&7)];
1256 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1);
1257 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1);
1261 color = particlepalette[226 + (rand()&7)];
1262 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);
1265 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1267 if (cl_particles_quake.integer)
1269 color = particlepalette[152 + (rand()&3)];
1270 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);
1272 else if (gamemode == GAME_GOODVSBAD2)
1275 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);
1277 else if (gamemode == GAME_PRYDON)
1280 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);
1283 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);
1285 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1288 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);
1290 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1293 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);
1295 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1296 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);
1300 if (effectnameindex == EFFECT_TR_ROCKET)
1301 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);
1302 else if (effectnameindex == EFFECT_TR_GRENADE)
1303 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);
1305 // advance to next time and position
1308 VectorMA (pos, dec, dir, pos);
1311 ent->persistent.trail_time = len;
1314 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1317 // this is also called on point effects with spawndlight = true and
1318 // spawnparticles = true
1319 // it is called CL_ParticleTrail because most code does not want to supply
1320 // these parameters, only trail handling does
1321 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)
1323 qboolean found = false;
1324 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1326 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1327 return; // no such effect
1329 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1331 int effectinfoindex;
1334 particleeffectinfo_t *info;
1341 qboolean underwater;
1342 qboolean immediatebloodstain;
1344 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1345 VectorLerp(originmins, 0.5, originmaxs, center);
1346 supercontents = CL_PointSuperContents(center);
1347 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1348 VectorSubtract(originmaxs, originmins, traildir);
1349 traillen = VectorLength(traildir);
1350 VectorNormalize(traildir);
1351 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1353 if (info->effectnameindex == effectnameindex)
1356 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1358 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1361 // spawn a dlight if requested
1362 if (info->lightradiusstart > 0 && spawndlight)
1364 matrix4x4_t tempmatrix;
1365 if (info->trailspacing > 0)
1366 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1368 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1369 if (info->lighttime > 0 && info->lightradiusfade > 0)
1371 // light flash (explosion, etc)
1372 // called when effect starts
1373 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);
1378 // called by CL_LinkNetworkEntity
1379 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1380 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);
1381 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1385 if (!spawnparticles)
1390 if (info->tex[1] > info->tex[0])
1392 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1393 tex = min(tex, info->tex[1] - 1);
1395 if(info->staintex[0] < 0)
1396 staintex = info->staintex[0];
1399 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1400 staintex = min(staintex, info->staintex[1] - 1);
1402 if (info->particletype == pt_decal)
1403 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]);
1404 else if (info->orientation == PARTICLE_HBEAM)
1405 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]));
1408 if (!cl_particles.integer)
1410 switch (info->particletype)
1412 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1413 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1414 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1415 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1416 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1417 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1420 VectorCopy(originmins, trailpos);
1421 if (info->trailspacing > 0)
1423 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1424 trailstep = info->trailspacing / cl_particles_quality.value;
1425 immediatebloodstain = false;
1429 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1431 immediatebloodstain = info->particletype == pt_blood || staintex;
1433 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1434 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1436 if (info->tex[1] > info->tex[0])
1438 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1439 tex = min(tex, info->tex[1] - 1);
1443 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1444 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1445 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1448 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]));
1449 if (immediatebloodstain && part)
1451 immediatebloodstain = false;
1452 CL_ImmediateBloodStain(part);
1455 VectorMA(trailpos, trailstep, traildir, trailpos);
1462 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1465 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)
1467 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1475 void CL_EntityParticles (const entity_t *ent)
1478 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1479 static vec3_t avelocities[NUMVERTEXNORMALS];
1480 if (!cl_particles.integer) return;
1481 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1483 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1485 if (!avelocities[0][0])
1486 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1487 avelocities[0][i] = lhrandom(0, 2.55);
1489 for (i = 0;i < NUMVERTEXNORMALS;i++)
1491 yaw = cl.time * avelocities[i][0];
1492 pitch = cl.time * avelocities[i][1];
1493 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1494 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1495 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1496 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);
1501 void CL_ReadPointFile_f (void)
1503 vec3_t org, leakorg;
1505 char *pointfile = NULL, *pointfilepos, *t, tchar;
1506 char name[MAX_OSPATH];
1511 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1512 strlcat (name, ".pts", sizeof (name));
1513 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1516 Con_Printf("Could not open %s\n", name);
1520 Con_Printf("Reading %s...\n", name);
1521 VectorClear(leakorg);
1524 pointfilepos = pointfile;
1525 while (*pointfilepos)
1527 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1532 while (*t && *t != '\n' && *t != '\r')
1536 #if _MSC_VER >= 1400
1537 #define sscanf sscanf_s
1539 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1545 VectorCopy(org, leakorg);
1548 if (cl.num_particles < cl.max_particles - 3)
1551 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);
1554 Mem_Free(pointfile);
1555 VectorCopy(leakorg, org);
1556 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1558 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);
1559 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);
1560 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);
1565 CL_ParseParticleEffect
1567 Parse an effect out of the server message
1570 void CL_ParseParticleEffect (void)
1573 int i, count, msgcount, color;
1575 MSG_ReadVector(org, cls.protocol);
1576 for (i=0 ; i<3 ; i++)
1577 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1578 msgcount = MSG_ReadByte ();
1579 color = MSG_ReadByte ();
1581 if (msgcount == 255)
1586 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1591 CL_ParticleExplosion
1595 void CL_ParticleExplosion (const vec3_t org)
1601 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1602 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1604 if (cl_particles_quake.integer)
1606 for (i = 0;i < 1024;i++)
1612 color = particlepalette[ramp1[r]];
1613 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);
1617 color = particlepalette[ramp2[r]];
1618 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);
1624 i = CL_PointSuperContents(org);
1625 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1627 if (cl_particles.integer && cl_particles_bubbles.integer)
1628 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1629 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);
1633 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1635 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1639 for (k = 0;k < 16;k++)
1642 VectorMA(org, 128, v2, v);
1643 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1644 if (trace.fraction >= 0.1)
1647 VectorSubtract(trace.endpos, org, v2);
1648 VectorScale(v2, 2.0f, v2);
1649 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);
1655 if (cl_particles_explosions_shell.integer)
1656 R_NewExplosion(org);
1661 CL_ParticleExplosion2
1665 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1668 if (!cl_particles.integer) return;
1670 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1672 k = particlepalette[colorStart + (i % colorLength)];
1673 if (cl_particles_quake.integer)
1674 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);
1676 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);
1680 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1683 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1684 if (cl_particles_sparks.integer)
1686 sparkcount *= cl_particles_quality.value;
1687 while(sparkcount-- > 0)
1688 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);
1692 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1695 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1696 if (cl_particles_smoke.integer)
1698 smokecount *= cl_particles_quality.value;
1699 while(smokecount-- > 0)
1700 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);
1704 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)
1708 if (!cl_particles.integer) return;
1709 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1711 count = (int)(count * cl_particles_quality.value);
1714 k = particlepalette[colorbase + (rand()&3)];
1715 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);
1719 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1722 float minz, maxz, lifetime = 30;
1724 if (!cl_particles.integer) return;
1725 if (dir[2] < 0) // falling
1727 minz = maxs[2] + dir[2] * 0.1;
1730 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1735 maxz = maxs[2] + dir[2] * 0.1;
1737 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1740 count = (int)(count * cl_particles_quality.value);
1745 if (!cl_particles_rain.integer) break;
1746 count *= 4; // ick, this should be in the mod or maps?
1750 k = particlepalette[colorbase + (rand()&3)];
1751 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1752 if (gamemode == GAME_GOODVSBAD2)
1753 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);
1755 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);
1759 if (!cl_particles_snow.integer) break;
1762 k = particlepalette[colorbase + (rand()&3)];
1763 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1764 if (gamemode == GAME_GOODVSBAD2)
1765 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);
1767 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);
1771 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1775 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1776 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1777 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1778 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1780 #define PARTICLETEXTURESIZE 64
1781 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1783 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1787 dz = 1 - (dx*dx+dy*dy);
1788 if (dz > 0) // it does hit the sphere
1792 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1793 VectorNormalize(normal);
1794 dot = DotProduct(normal, light);
1795 if (dot > 0.5) // interior reflection
1796 f += ((dot * 2) - 1);
1797 else if (dot < -0.5) // exterior reflection
1798 f += ((dot * -2) - 1);
1800 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1801 VectorNormalize(normal);
1802 dot = DotProduct(normal, light);
1803 if (dot > 0.5) // interior reflection
1804 f += ((dot * 2) - 1);
1805 else if (dot < -0.5) // exterior reflection
1806 f += ((dot * -2) - 1);
1808 f += 16; // just to give it a haze so you can see the outline
1809 f = bound(0, f, 255);
1810 return (unsigned char) f;
1816 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1817 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1819 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1820 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1821 *width = particlefontcellwidth;
1822 *height = particlefontcellheight;
1825 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1827 int basex, basey, w, h, y;
1828 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1829 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1830 Sys_Error("invalid particle texture size for autogenerating");
1831 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1832 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1835 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1838 float cx, cy, dx, dy, f, iradius;
1840 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1841 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1842 iradius = 1.0f / radius;
1843 alpha *= (1.0f / 255.0f);
1844 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1846 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1850 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1855 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1856 d[0] += (int)(f * (blue - d[0]));
1857 d[1] += (int)(f * (green - d[1]));
1858 d[2] += (int)(f * (red - d[2]));
1864 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1867 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1869 data[0] = bound(minb, data[0], maxb);
1870 data[1] = bound(ming, data[1], maxg);
1871 data[2] = bound(minr, data[2], maxr);
1875 void particletextureinvert(unsigned char *data)
1878 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1880 data[0] = 255 - data[0];
1881 data[1] = 255 - data[1];
1882 data[2] = 255 - data[2];
1886 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1887 static void R_InitBloodTextures (unsigned char *particletexturedata)
1890 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1891 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1894 for (i = 0;i < 8;i++)
1896 memset(data, 255, datasize);
1897 for (k = 0;k < 24;k++)
1898 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1899 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1900 particletextureinvert(data);
1901 setuptex(tex_bloodparticle[i], data, particletexturedata);
1905 for (i = 0;i < 8;i++)
1907 memset(data, 255, datasize);
1909 for (j = 1;j < 10;j++)
1910 for (k = min(j, m - 1);k < m;k++)
1911 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1912 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1913 particletextureinvert(data);
1914 setuptex(tex_blooddecal[i], data, particletexturedata);
1920 //uncomment this to make engine save out particle font to a tga file when run
1921 //#define DUMPPARTICLEFONT
1923 static void R_InitParticleTexture (void)
1925 int x, y, d, i, k, m;
1926 int basex, basey, w, h;
1930 fs_offset_t filesize;
1932 // a note: decals need to modulate (multiply) the background color to
1933 // properly darken it (stain), and they need to be able to alpha fade,
1934 // this is a very difficult challenge because it means fading to white
1935 // (no change to background) rather than black (darkening everything
1936 // behind the whole decal polygon), and to accomplish this the texture is
1937 // inverted (dark red blood on white background becomes brilliant cyan
1938 // and white on black background) so we can alpha fade it to black, then
1939 // we invert it again during the blendfunc to make it work...
1941 #ifndef DUMPPARTICLEFONT
1942 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1945 particlefonttexture = decalskinframe->base;
1946 // TODO maybe allow custom grid size?
1947 particlefontwidth = image_width;
1948 particlefontheight = image_height;
1949 particlefontcellwidth = image_width / 8;
1950 particlefontcellheight = image_height / 8;
1951 particlefontcols = 8;
1952 particlefontrows = 8;
1957 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1958 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1959 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1960 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1961 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1963 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1964 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1965 particlefontcols = 8;
1966 particlefontrows = 8;
1968 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1971 for (i = 0;i < 8;i++)
1973 memset(data, 255, datasize);
1976 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1977 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1979 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1981 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1982 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1984 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1985 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
1987 d = (int)(d * (1-(dx*dx+dy*dy)));
1988 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
1989 d = bound(0, d, 255);
1990 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
1997 setuptex(tex_smoke[i], data, particletexturedata);
2001 memset(data, 255, datasize);
2002 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2004 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2005 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2007 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2008 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2009 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2012 setuptex(tex_rainsplash, data, particletexturedata);
2015 memset(data, 255, datasize);
2016 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2018 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2019 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2021 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2022 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2023 d = bound(0, d, 255);
2024 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2027 setuptex(tex_particle, data, particletexturedata);
2030 memset(data, 255, datasize);
2031 light[0] = 1;light[1] = 1;light[2] = 1;
2032 VectorNormalize(light);
2033 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2035 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2036 // stretch upper half of bubble by +50% and shrink lower half by -50%
2037 // (this gives an elongated teardrop shape)
2039 dy = (dy - 0.5f) * 2.0f;
2041 dy = (dy - 0.5f) / 1.5f;
2042 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2044 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2045 // shrink bubble width to half
2047 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2050 setuptex(tex_raindrop, data, particletexturedata);
2053 memset(data, 255, datasize);
2054 light[0] = 1;light[1] = 1;light[2] = 1;
2055 VectorNormalize(light);
2056 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2058 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2059 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2061 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2062 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2065 setuptex(tex_bubble, data, particletexturedata);
2067 // Blood particles and blood decals
2068 R_InitBloodTextures (particletexturedata);
2071 for (i = 0;i < 8;i++)
2073 memset(data, 255, datasize);
2074 for (k = 0;k < 12;k++)
2075 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2076 for (k = 0;k < 3;k++)
2077 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2078 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2079 particletextureinvert(data);
2080 setuptex(tex_bulletdecal[i], data, particletexturedata);
2083 #ifdef DUMPPARTICLEFONT
2084 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2087 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2088 particlefonttexture = decalskinframe->base;
2090 Mem_Free(particletexturedata);
2095 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2097 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2098 particletexture[i].texture = particlefonttexture;
2099 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2100 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2101 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2102 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2105 #ifndef DUMPPARTICLEFONT
2106 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
2107 if (!particletexture[tex_beam].texture)
2110 unsigned char noise3[64][64], data2[64][16][4];
2112 fractalnoise(&noise3[0][0], 64, 4);
2114 for (y = 0;y < 64;y++)
2116 dy = (y - 0.5f*64) / (64*0.5f-1);
2117 for (x = 0;x < 16;x++)
2119 dx = (x - 0.5f*16) / (16*0.5f-2);
2120 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2121 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2122 data2[y][x][3] = 255;
2126 #ifdef DUMPPARTICLEFONT
2127 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2129 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2131 particletexture[tex_beam].s1 = 0;
2132 particletexture[tex_beam].t1 = 0;
2133 particletexture[tex_beam].s2 = 1;
2134 particletexture[tex_beam].t2 = 1;
2136 // now load an texcoord/texture override file
2137 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2144 if(!COM_ParseToken_Simple(&bufptr, true, false))
2146 if(!strcmp(com_token, "\n"))
2147 continue; // empty line
2148 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2149 particletexture[i].texture = particlefonttexture;
2151 if (!COM_ParseToken_Simple(&bufptr, true, false))
2153 if (!strcmp(com_token, "\n"))
2155 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2158 particletexture[i].s1 = atof(com_token);
2160 if (!COM_ParseToken_Simple(&bufptr, true, false))
2162 if (!strcmp(com_token, "\n"))
2164 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2167 particletexture[i].t1 = atof(com_token);
2169 if (!COM_ParseToken_Simple(&bufptr, true, false))
2171 if (!strcmp(com_token, "\n"))
2173 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2176 particletexture[i].s2 = atof(com_token);
2178 if (!COM_ParseToken_Simple(&bufptr, true, false))
2180 if (!strcmp(com_token, "\n"))
2182 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2185 particletexture[i].t2 = atof(com_token);
2191 static void r_part_start(void)
2194 // generate particlepalette for convenience from the main one
2195 for (i = 0;i < 256;i++)
2196 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2197 particletexturepool = R_AllocTexturePool();
2198 R_InitParticleTexture ();
2199 CL_Particles_LoadEffectInfo();
2202 static void r_part_shutdown(void)
2204 R_FreeTexturePool(&particletexturepool);
2207 static void r_part_newmap(void)
2210 R_SkinFrame_MarkUsed(decalskinframe);
2211 CL_Particles_LoadEffectInfo();
2214 #define BATCHSIZE 256
2215 unsigned short particle_elements[BATCHSIZE*6];
2216 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2218 void R_Particles_Init (void)
2221 for (i = 0;i < BATCHSIZE;i++)
2223 particle_elements[i*6+0] = i*4+0;
2224 particle_elements[i*6+1] = i*4+1;
2225 particle_elements[i*6+2] = i*4+2;
2226 particle_elements[i*6+3] = i*4+0;
2227 particle_elements[i*6+4] = i*4+2;
2228 particle_elements[i*6+5] = i*4+3;
2231 Cvar_RegisterVariable(&r_drawparticles);
2232 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2233 Cvar_RegisterVariable(&r_drawdecals);
2234 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2235 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2238 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2240 int surfacelistindex;
2242 float *v3f, *t2f, *c4f;
2243 particletexture_t *tex;
2244 float right[3], up[3], size, ca;
2245 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2246 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2248 RSurf_ActiveWorldEntity();
2250 r_refdef.stats.drawndecals += numsurfaces;
2251 R_Mesh_ResetTextureState();
2252 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2253 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2254 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2255 GL_DepthMask(false);
2256 GL_DepthRange(0, 1);
2257 GL_PolygonOffset(0, 0);
2259 GL_CullFace(GL_NONE);
2261 // generate all the vertices at once
2262 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2264 d = cl.decals + surfacelist[surfacelistindex];
2267 c4f = particle_color4f + 16*surfacelistindex;
2268 ca = d->alpha * alphascale;
2269 // ensure alpha multiplier saturates properly
2270 if (ca > 1.0f / 256.0f)
2272 if (r_refdef.fogenabled)
2273 ca *= RSurf_FogVertex(d->org);
2274 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2275 Vector4Copy(c4f, c4f + 4);
2276 Vector4Copy(c4f, c4f + 8);
2277 Vector4Copy(c4f, c4f + 12);
2279 // calculate vertex positions
2280 size = d->size * cl_particles_size.value;
2281 VectorVectors(d->normal, right, up);
2282 VectorScale(right, size, right);
2283 VectorScale(up, size, up);
2284 v3f = particle_vertex3f + 12*surfacelistindex;
2285 v3f[ 0] = d->org[0] - right[0] - up[0];
2286 v3f[ 1] = d->org[1] - right[1] - up[1];
2287 v3f[ 2] = d->org[2] - right[2] - up[2];
2288 v3f[ 3] = d->org[0] - right[0] + up[0];
2289 v3f[ 4] = d->org[1] - right[1] + up[1];
2290 v3f[ 5] = d->org[2] - right[2] + up[2];
2291 v3f[ 6] = d->org[0] + right[0] + up[0];
2292 v3f[ 7] = d->org[1] + right[1] + up[1];
2293 v3f[ 8] = d->org[2] + right[2] + up[2];
2294 v3f[ 9] = d->org[0] + right[0] - up[0];
2295 v3f[10] = d->org[1] + right[1] - up[1];
2296 v3f[11] = d->org[2] + right[2] - up[2];
2298 // calculate texcoords
2299 tex = &particletexture[d->texnum];
2300 t2f = particle_texcoord2f + 8*surfacelistindex;
2301 t2f[0] = tex->s1;t2f[1] = tex->t2;
2302 t2f[2] = tex->s1;t2f[3] = tex->t1;
2303 t2f[4] = tex->s2;t2f[5] = tex->t1;
2304 t2f[6] = tex->s2;t2f[7] = tex->t2;
2307 // now render the decals all at once
2308 // (this assumes they all use one particle font texture!)
2309 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2310 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2311 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2314 void R_DrawDecals (void)
2317 int drawdecals = r_drawdecals.integer;
2322 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2324 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2325 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2327 // LordHavoc: early out conditions
2331 decalfade = frametime * 256 / cl_decals_fadetime.value;
2332 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2333 drawdist2 = drawdist2*drawdist2;
2335 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2337 if (!decal->typeindex)
2340 if (killsequence - decal->decalsequence > 0)
2343 if (cl.time > decal->time2 + cl_decals_time.value)
2345 decal->alpha -= decalfade;
2346 if (decal->alpha <= 0)
2352 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2354 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2355 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2361 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2367 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))
2368 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2371 decal->typeindex = 0;
2372 if (cl.free_decal > i)
2376 // reduce cl.num_decals if possible
2377 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2380 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2382 decal_t *olddecals = cl.decals;
2383 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2384 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2385 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2386 Mem_Free(olddecals);
2389 r_refdef.stats.totaldecals = cl.num_decals;
2392 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2394 int surfacelistindex;
2395 int batchstart, batchcount;
2396 const particle_t *p;
2398 rtexture_t *texture;
2399 float *v3f, *t2f, *c4f;
2400 particletexture_t *tex;
2401 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2402 float ambient[3], diffuse[3], diffusenormal[3];
2403 vec4_t colormultiplier;
2405 RSurf_ActiveWorldEntity();
2407 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));
2409 r_refdef.stats.particles += numsurfaces;
2410 R_Mesh_ResetTextureState();
2411 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2412 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2413 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2414 GL_DepthMask(false);
2415 GL_DepthRange(0, 1);
2416 GL_PolygonOffset(0, 0);
2418 GL_CullFace(GL_NONE);
2420 // first generate all the vertices at once
2421 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2423 p = cl.particles + surfacelist[surfacelistindex];
2425 blendmode = p->blendmode;
2429 case PBLEND_INVALID:
2431 alpha = p->alpha * colormultiplier[3];
2432 // ensure alpha multiplier saturates properly
2435 // additive and modulate can just fade out in fog (this is correct)
2436 if (r_refdef.fogenabled)
2437 alpha *= RSurf_FogVertex(p->org);
2438 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2439 alpha *= 1.0f / 256.0f;
2440 c4f[0] = p->color[0] * alpha;
2441 c4f[1] = p->color[1] * alpha;
2442 c4f[2] = p->color[2] * alpha;
2446 alpha = p->alpha * colormultiplier[3];
2447 // ensure alpha multiplier saturates properly
2450 // additive and modulate can just fade out in fog (this is correct)
2451 if (r_refdef.fogenabled)
2452 alpha *= RSurf_FogVertex(p->org);
2453 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2454 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2455 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2456 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2460 c4f[0] = p->color[0] * colormultiplier[0];
2461 c4f[1] = p->color[1] * colormultiplier[1];
2462 c4f[2] = p->color[2] * colormultiplier[2];
2463 c4f[3] = p->alpha * colormultiplier[3];
2464 // note: lighting is not cheap!
2465 if (particletype[p->typeindex].lighting)
2467 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2468 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2469 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2470 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2472 // mix in the fog color
2473 if (r_refdef.fogenabled)
2475 fog = RSurf_FogVertex(p->org);
2477 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2478 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2479 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2483 // copy the color into the other three vertices
2484 Vector4Copy(c4f, c4f + 4);
2485 Vector4Copy(c4f, c4f + 8);
2486 Vector4Copy(c4f, c4f + 12);
2488 size = p->size * cl_particles_size.value;
2489 tex = &particletexture[p->texnum];
2490 switch(p->orientation)
2492 case PARTICLE_INVALID:
2493 case PARTICLE_BILLBOARD:
2494 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2495 VectorScale(r_refdef.view.up, size, up);
2496 v3f[ 0] = p->org[0] - right[0] - up[0];
2497 v3f[ 1] = p->org[1] - right[1] - up[1];
2498 v3f[ 2] = p->org[2] - right[2] - up[2];
2499 v3f[ 3] = p->org[0] - right[0] + up[0];
2500 v3f[ 4] = p->org[1] - right[1] + up[1];
2501 v3f[ 5] = p->org[2] - right[2] + up[2];
2502 v3f[ 6] = p->org[0] + right[0] + up[0];
2503 v3f[ 7] = p->org[1] + right[1] + up[1];
2504 v3f[ 8] = p->org[2] + right[2] + up[2];
2505 v3f[ 9] = p->org[0] + right[0] - up[0];
2506 v3f[10] = p->org[1] + right[1] - up[1];
2507 v3f[11] = p->org[2] + right[2] - up[2];
2508 t2f[0] = tex->s1;t2f[1] = tex->t2;
2509 t2f[2] = tex->s1;t2f[3] = tex->t1;
2510 t2f[4] = tex->s2;t2f[5] = tex->t1;
2511 t2f[6] = tex->s2;t2f[7] = tex->t2;
2513 case PARTICLE_ORIENTED_DOUBLESIDED:
2514 VectorVectors(p->vel, right, up);
2515 VectorScale(right, size * p->stretch, right);
2516 VectorScale(up, size, up);
2517 v3f[ 0] = p->org[0] - right[0] - up[0];
2518 v3f[ 1] = p->org[1] - right[1] - up[1];
2519 v3f[ 2] = p->org[2] - right[2] - up[2];
2520 v3f[ 3] = p->org[0] - right[0] + up[0];
2521 v3f[ 4] = p->org[1] - right[1] + up[1];
2522 v3f[ 5] = p->org[2] - right[2] + up[2];
2523 v3f[ 6] = p->org[0] + right[0] + up[0];
2524 v3f[ 7] = p->org[1] + right[1] + up[1];
2525 v3f[ 8] = p->org[2] + right[2] + up[2];
2526 v3f[ 9] = p->org[0] + right[0] - up[0];
2527 v3f[10] = p->org[1] + right[1] - up[1];
2528 v3f[11] = p->org[2] + right[2] - up[2];
2529 t2f[0] = tex->s1;t2f[1] = tex->t2;
2530 t2f[2] = tex->s1;t2f[3] = tex->t1;
2531 t2f[4] = tex->s2;t2f[5] = tex->t1;
2532 t2f[6] = tex->s2;t2f[7] = tex->t2;
2534 case PARTICLE_SPARK:
2535 len = VectorLength(p->vel);
2536 VectorNormalize2(p->vel, up);
2537 lenfactor = p->stretch * 0.04 * len;
2538 if(lenfactor < size * 0.5)
2539 lenfactor = size * 0.5;
2540 VectorMA(p->org, -lenfactor, up, v);
2541 VectorMA(p->org, lenfactor, up, up2);
2542 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2543 t2f[0] = tex->s1;t2f[1] = tex->t2;
2544 t2f[2] = tex->s1;t2f[3] = tex->t1;
2545 t2f[4] = tex->s2;t2f[5] = tex->t1;
2546 t2f[6] = tex->s2;t2f[7] = tex->t2;
2548 case PARTICLE_VBEAM:
2549 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2550 VectorSubtract(p->vel, p->org, up);
2551 VectorNormalize(up);
2552 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2553 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2554 t2f[0] = tex->s2;t2f[1] = v[0];
2555 t2f[2] = tex->s1;t2f[3] = v[0];
2556 t2f[4] = tex->s1;t2f[5] = v[1];
2557 t2f[6] = tex->s2;t2f[7] = v[1];
2559 case PARTICLE_HBEAM:
2560 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2561 VectorSubtract(p->vel, p->org, up);
2562 VectorNormalize(up);
2563 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2564 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2565 t2f[0] = v[0];t2f[1] = tex->t1;
2566 t2f[2] = v[0];t2f[3] = tex->t2;
2567 t2f[4] = v[1];t2f[5] = tex->t2;
2568 t2f[6] = v[1];t2f[7] = tex->t1;
2573 // now render batches of particles based on blendmode and texture
2574 blendmode = PBLEND_INVALID;
2578 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2580 p = cl.particles + surfacelist[surfacelistindex];
2582 if (blendmode != p->blendmode)
2584 blendmode = p->blendmode;
2588 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2590 case PBLEND_INVALID:
2592 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2595 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2599 if (texture != particletexture[p->texnum].texture)
2601 texture = particletexture[p->texnum].texture;
2602 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2605 // iterate until we find a change in settings
2606 batchstart = surfacelistindex++;
2607 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2609 p = cl.particles + surfacelist[surfacelistindex];
2610 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2614 batchcount = surfacelistindex - batchstart;
2615 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2619 void R_DrawParticles (void)
2622 int drawparticles = r_drawparticles.integer;
2623 float minparticledist;
2625 float gravity, frametime, f, dist, oldorg[3];
2631 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2632 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2634 // LordHavoc: early out conditions
2635 if (!cl.num_particles)
2638 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2639 gravity = frametime * cl.movevars_gravity;
2640 update = frametime > 0;
2641 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2642 drawdist2 = drawdist2*drawdist2;
2644 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2648 if (cl.free_particle > i)
2649 cl.free_particle = i;
2655 if (p->delayedspawn > cl.time)
2657 p->delayedspawn = 0;
2659 p->size += p->sizeincrease * frametime;
2660 p->alpha -= p->alphafade * frametime;
2662 if (p->alpha <= 0 || p->die <= cl.time)
2665 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2667 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2669 if (p->typeindex == pt_blood)
2670 p->size += frametime * 8;
2672 p->vel[2] -= p->gravity * gravity;
2673 f = 1.0f - min(p->liquidfriction * frametime, 1);
2674 VectorScale(p->vel, f, p->vel);
2678 p->vel[2] -= p->gravity * gravity;
2681 f = 1.0f - min(p->airfriction * frametime, 1);
2682 VectorScale(p->vel, f, p->vel);
2686 VectorCopy(p->org, oldorg);
2687 VectorMA(p->org, frametime, p->vel, p->org);
2688 if (p->bounce && cl.time >= p->delayedcollisions)
2690 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);
2691 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2692 // or if the trace hit something flagged as NOIMPACT
2693 // then remove the particle
2694 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2696 VectorCopy(trace.endpos, p->org);
2697 // react if the particle hit something
2698 if (trace.fraction < 1)
2700 VectorCopy(trace.endpos, p->org);
2702 if (p->staintexnum >= 0)
2704 // blood - splash on solid
2705 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2708 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2709 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2710 if (cl_decals.integer)
2712 // create a decal for the blood splat
2713 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, 0xFFFFFF ^ p->staincolor, 0xFFFFFF ^ p->staincolor, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2718 if (p->typeindex == pt_blood)
2720 // blood - splash on solid
2721 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2723 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2725 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)));
2726 if (cl_decals.integer)
2728 // create a decal for the blood splat
2729 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);
2734 else if (p->bounce < 0)
2736 // bounce -1 means remove on impact
2741 // anything else - bounce off solid
2742 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2743 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2744 if (DotProduct(p->vel, p->vel) < 0.03)
2745 VectorClear(p->vel);
2751 if (p->typeindex != pt_static)
2753 switch (p->typeindex)
2755 case pt_entityparticle:
2756 // particle that removes itself after one rendered frame
2763 a = CL_PointSuperContents(p->org);
2764 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2768 a = CL_PointSuperContents(p->org);
2769 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2773 a = CL_PointSuperContents(p->org);
2774 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2778 if (cl.time > p->time2)
2781 p->time2 = cl.time + (rand() & 3) * 0.1;
2782 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2783 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2785 a = CL_PointSuperContents(p->org);
2786 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2794 else if (p->delayedspawn)
2798 // don't render particles too close to the view (they chew fillrate)
2799 // also don't render particles behind the view (useless)
2800 // further checks to cull to the frustum would be too slow here
2801 switch(p->typeindex)
2804 // beams have no culling
2805 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2808 if(cl_particles_visculling.integer)
2809 if (!r_refdef.viewcache.world_novis)
2810 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2812 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2814 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2817 // anything else just has to be in front of the viewer and visible at this distance
2818 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2819 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2826 if (cl.free_particle > i)
2827 cl.free_particle = i;
2830 // reduce cl.num_particles if possible
2831 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2834 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2836 particle_t *oldparticles = cl.particles;
2837 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2838 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2839 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2840 Mem_Free(oldparticles);