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;
1927 float dx, dy, f, s1, t1, s2, t2;
1930 fs_offset_t filesize;
1931 char texturename[MAX_QPATH];
1933 // a note: decals need to modulate (multiply) the background color to
1934 // properly darken it (stain), and they need to be able to alpha fade,
1935 // this is a very difficult challenge because it means fading to white
1936 // (no change to background) rather than black (darkening everything
1937 // behind the whole decal polygon), and to accomplish this the texture is
1938 // inverted (dark red blood on white background becomes brilliant cyan
1939 // and white on black background) so we can alpha fade it to black, then
1940 // we invert it again during the blendfunc to make it work...
1942 #ifndef DUMPPARTICLEFONT
1943 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1946 particlefonttexture = decalskinframe->base;
1947 // TODO maybe allow custom grid size?
1948 particlefontwidth = image_width;
1949 particlefontheight = image_height;
1950 particlefontcellwidth = image_width / 8;
1951 particlefontcellheight = image_height / 8;
1952 particlefontcols = 8;
1953 particlefontrows = 8;
1958 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1959 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1960 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1961 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1962 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1964 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1965 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1966 particlefontcols = 8;
1967 particlefontrows = 8;
1969 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1972 for (i = 0;i < 8;i++)
1974 memset(data, 255, datasize);
1977 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1978 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1980 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1982 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1983 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1985 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1986 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
1988 d = (int)(d * (1-(dx*dx+dy*dy)));
1989 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
1990 d = bound(0, d, 255);
1991 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
1998 setuptex(tex_smoke[i], data, particletexturedata);
2002 memset(data, 255, datasize);
2003 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2005 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2006 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2008 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2009 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2010 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2013 setuptex(tex_rainsplash, data, particletexturedata);
2016 memset(data, 255, datasize);
2017 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2019 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2020 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2022 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2023 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2024 d = bound(0, d, 255);
2025 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2028 setuptex(tex_particle, data, particletexturedata);
2031 memset(data, 255, datasize);
2032 light[0] = 1;light[1] = 1;light[2] = 1;
2033 VectorNormalize(light);
2034 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2036 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2037 // stretch upper half of bubble by +50% and shrink lower half by -50%
2038 // (this gives an elongated teardrop shape)
2040 dy = (dy - 0.5f) * 2.0f;
2042 dy = (dy - 0.5f) / 1.5f;
2043 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2045 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2046 // shrink bubble width to half
2048 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2051 setuptex(tex_raindrop, data, particletexturedata);
2054 memset(data, 255, datasize);
2055 light[0] = 1;light[1] = 1;light[2] = 1;
2056 VectorNormalize(light);
2057 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2059 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2060 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2062 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2063 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2066 setuptex(tex_bubble, data, particletexturedata);
2068 // Blood particles and blood decals
2069 R_InitBloodTextures (particletexturedata);
2072 for (i = 0;i < 8;i++)
2074 memset(data, 255, datasize);
2075 for (k = 0;k < 12;k++)
2076 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2077 for (k = 0;k < 3;k++)
2078 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2079 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2080 particletextureinvert(data);
2081 setuptex(tex_bulletdecal[i], data, particletexturedata);
2084 #ifdef DUMPPARTICLEFONT
2085 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2088 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2089 particlefonttexture = decalskinframe->base;
2091 Mem_Free(particletexturedata);
2096 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2098 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2099 particletexture[i].texture = particlefonttexture;
2100 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2101 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2102 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2103 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2106 #ifndef DUMPPARTICLEFONT
2107 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
2108 if (!particletexture[tex_beam].texture)
2111 unsigned char noise3[64][64], data2[64][16][4];
2113 fractalnoise(&noise3[0][0], 64, 4);
2115 for (y = 0;y < 64;y++)
2117 dy = (y - 0.5f*64) / (64*0.5f-1);
2118 for (x = 0;x < 16;x++)
2120 dx = (x - 0.5f*16) / (16*0.5f-2);
2121 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2122 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2123 data2[y][x][3] = 255;
2127 #ifdef DUMPPARTICLEFONT
2128 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2130 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2132 particletexture[tex_beam].s1 = 0;
2133 particletexture[tex_beam].t1 = 0;
2134 particletexture[tex_beam].s2 = 1;
2135 particletexture[tex_beam].t2 = 1;
2137 // now load an texcoord/texture override file
2138 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2145 if(!COM_ParseToken_Simple(&bufptr, true, false))
2147 if(!strcmp(com_token, "\n"))
2148 continue; // empty line
2149 i = atoi(com_token);
2157 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2159 s1 = atof(com_token);
2160 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2162 t1 = atof(com_token);
2163 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2165 s2 = atof(com_token);
2166 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2168 t2 = atof(com_token);
2169 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2170 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2171 strlcpy(texturename, com_token, sizeof(texturename));
2178 strlcpy(texturename, com_token, sizeof(texturename));
2181 if (!texturename[0])
2183 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2186 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2188 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2191 particletexture[i].texture = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR, false)->base;
2192 particletexture[i].s1 = s1;
2193 particletexture[i].t1 = t1;
2194 particletexture[i].s2 = s2;
2195 particletexture[i].t2 = t2;
2201 static void r_part_start(void)
2204 // generate particlepalette for convenience from the main one
2205 for (i = 0;i < 256;i++)
2206 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2207 particletexturepool = R_AllocTexturePool();
2208 R_InitParticleTexture ();
2209 CL_Particles_LoadEffectInfo();
2212 static void r_part_shutdown(void)
2214 R_FreeTexturePool(&particletexturepool);
2217 static void r_part_newmap(void)
2220 R_SkinFrame_MarkUsed(decalskinframe);
2221 CL_Particles_LoadEffectInfo();
2224 #define BATCHSIZE 256
2225 unsigned short particle_elements[BATCHSIZE*6];
2226 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2228 void R_Particles_Init (void)
2231 for (i = 0;i < BATCHSIZE;i++)
2233 particle_elements[i*6+0] = i*4+0;
2234 particle_elements[i*6+1] = i*4+1;
2235 particle_elements[i*6+2] = i*4+2;
2236 particle_elements[i*6+3] = i*4+0;
2237 particle_elements[i*6+4] = i*4+2;
2238 particle_elements[i*6+5] = i*4+3;
2241 Cvar_RegisterVariable(&r_drawparticles);
2242 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2243 Cvar_RegisterVariable(&r_drawdecals);
2244 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2245 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2248 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2250 int surfacelistindex;
2252 float *v3f, *t2f, *c4f;
2253 particletexture_t *tex;
2254 float right[3], up[3], size, ca;
2255 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2256 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2258 RSurf_ActiveWorldEntity();
2260 r_refdef.stats.drawndecals += numsurfaces;
2261 R_Mesh_ResetTextureState();
2262 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2263 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2264 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2265 GL_DepthMask(false);
2266 GL_DepthRange(0, 1);
2267 GL_PolygonOffset(0, 0);
2269 GL_CullFace(GL_NONE);
2271 // generate all the vertices at once
2272 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2274 d = cl.decals + surfacelist[surfacelistindex];
2277 c4f = particle_color4f + 16*surfacelistindex;
2278 ca = d->alpha * alphascale;
2279 // ensure alpha multiplier saturates properly
2280 if (ca > 1.0f / 256.0f)
2282 if (r_refdef.fogenabled)
2283 ca *= RSurf_FogVertex(d->org);
2284 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2285 Vector4Copy(c4f, c4f + 4);
2286 Vector4Copy(c4f, c4f + 8);
2287 Vector4Copy(c4f, c4f + 12);
2289 // calculate vertex positions
2290 size = d->size * cl_particles_size.value;
2291 VectorVectors(d->normal, right, up);
2292 VectorScale(right, size, right);
2293 VectorScale(up, size, up);
2294 v3f = particle_vertex3f + 12*surfacelistindex;
2295 v3f[ 0] = d->org[0] - right[0] - up[0];
2296 v3f[ 1] = d->org[1] - right[1] - up[1];
2297 v3f[ 2] = d->org[2] - right[2] - up[2];
2298 v3f[ 3] = d->org[0] - right[0] + up[0];
2299 v3f[ 4] = d->org[1] - right[1] + up[1];
2300 v3f[ 5] = d->org[2] - right[2] + up[2];
2301 v3f[ 6] = d->org[0] + right[0] + up[0];
2302 v3f[ 7] = d->org[1] + right[1] + up[1];
2303 v3f[ 8] = d->org[2] + right[2] + up[2];
2304 v3f[ 9] = d->org[0] + right[0] - up[0];
2305 v3f[10] = d->org[1] + right[1] - up[1];
2306 v3f[11] = d->org[2] + right[2] - up[2];
2308 // calculate texcoords
2309 tex = &particletexture[d->texnum];
2310 t2f = particle_texcoord2f + 8*surfacelistindex;
2311 t2f[0] = tex->s1;t2f[1] = tex->t2;
2312 t2f[2] = tex->s1;t2f[3] = tex->t1;
2313 t2f[4] = tex->s2;t2f[5] = tex->t1;
2314 t2f[6] = tex->s2;t2f[7] = tex->t2;
2317 // now render the decals all at once
2318 // (this assumes they all use one particle font texture!)
2319 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2320 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2321 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2324 void R_DrawDecals (void)
2327 int drawdecals = r_drawdecals.integer;
2332 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2334 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2335 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2337 // LordHavoc: early out conditions
2341 decalfade = frametime * 256 / cl_decals_fadetime.value;
2342 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2343 drawdist2 = drawdist2*drawdist2;
2345 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2347 if (!decal->typeindex)
2350 if (killsequence - decal->decalsequence > 0)
2353 if (cl.time > decal->time2 + cl_decals_time.value)
2355 decal->alpha -= decalfade;
2356 if (decal->alpha <= 0)
2362 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2364 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2365 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2371 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2377 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))
2378 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2381 decal->typeindex = 0;
2382 if (cl.free_decal > i)
2386 // reduce cl.num_decals if possible
2387 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2390 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2392 decal_t *olddecals = cl.decals;
2393 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2394 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2395 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2396 Mem_Free(olddecals);
2399 r_refdef.stats.totaldecals = cl.num_decals;
2402 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2404 int surfacelistindex;
2405 int batchstart, batchcount;
2406 const particle_t *p;
2408 rtexture_t *texture;
2409 float *v3f, *t2f, *c4f;
2410 particletexture_t *tex;
2411 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2412 float ambient[3], diffuse[3], diffusenormal[3];
2413 vec4_t colormultiplier;
2415 RSurf_ActiveWorldEntity();
2417 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));
2419 r_refdef.stats.particles += numsurfaces;
2420 R_Mesh_ResetTextureState();
2421 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2422 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2423 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2424 GL_DepthMask(false);
2425 GL_DepthRange(0, 1);
2426 GL_PolygonOffset(0, 0);
2428 GL_AlphaTest(false);
2429 GL_CullFace(GL_NONE);
2431 // first generate all the vertices at once
2432 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2434 p = cl.particles + surfacelist[surfacelistindex];
2436 blendmode = p->blendmode;
2440 case PBLEND_INVALID:
2442 alpha = p->alpha * colormultiplier[3];
2443 // ensure alpha multiplier saturates properly
2446 // additive and modulate can just fade out in fog (this is correct)
2447 if (r_refdef.fogenabled)
2448 alpha *= RSurf_FogVertex(p->org);
2449 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2450 alpha *= 1.0f / 256.0f;
2451 c4f[0] = p->color[0] * alpha;
2452 c4f[1] = p->color[1] * alpha;
2453 c4f[2] = p->color[2] * alpha;
2457 alpha = p->alpha * colormultiplier[3];
2458 // ensure alpha multiplier saturates properly
2461 // additive and modulate can just fade out in fog (this is correct)
2462 if (r_refdef.fogenabled)
2463 alpha *= RSurf_FogVertex(p->org);
2464 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2465 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2466 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2467 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2471 c4f[0] = p->color[0] * colormultiplier[0];
2472 c4f[1] = p->color[1] * colormultiplier[1];
2473 c4f[2] = p->color[2] * colormultiplier[2];
2474 c4f[3] = p->alpha * colormultiplier[3];
2475 // note: lighting is not cheap!
2476 if (particletype[p->typeindex].lighting)
2478 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2479 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2480 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2481 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2483 // mix in the fog color
2484 if (r_refdef.fogenabled)
2486 fog = RSurf_FogVertex(p->org);
2488 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2489 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2490 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2494 // copy the color into the other three vertices
2495 Vector4Copy(c4f, c4f + 4);
2496 Vector4Copy(c4f, c4f + 8);
2497 Vector4Copy(c4f, c4f + 12);
2499 size = p->size * cl_particles_size.value;
2500 tex = &particletexture[p->texnum];
2501 switch(p->orientation)
2503 case PARTICLE_INVALID:
2504 case PARTICLE_BILLBOARD:
2505 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2506 VectorScale(r_refdef.view.up, size, up);
2507 v3f[ 0] = p->org[0] - right[0] - up[0];
2508 v3f[ 1] = p->org[1] - right[1] - up[1];
2509 v3f[ 2] = p->org[2] - right[2] - up[2];
2510 v3f[ 3] = p->org[0] - right[0] + up[0];
2511 v3f[ 4] = p->org[1] - right[1] + up[1];
2512 v3f[ 5] = p->org[2] - right[2] + up[2];
2513 v3f[ 6] = p->org[0] + right[0] + up[0];
2514 v3f[ 7] = p->org[1] + right[1] + up[1];
2515 v3f[ 8] = p->org[2] + right[2] + up[2];
2516 v3f[ 9] = p->org[0] + right[0] - up[0];
2517 v3f[10] = p->org[1] + right[1] - up[1];
2518 v3f[11] = p->org[2] + right[2] - up[2];
2519 t2f[0] = tex->s1;t2f[1] = tex->t2;
2520 t2f[2] = tex->s1;t2f[3] = tex->t1;
2521 t2f[4] = tex->s2;t2f[5] = tex->t1;
2522 t2f[6] = tex->s2;t2f[7] = tex->t2;
2524 case PARTICLE_ORIENTED_DOUBLESIDED:
2525 VectorVectors(p->vel, right, up);
2526 VectorScale(right, size * p->stretch, right);
2527 VectorScale(up, size, up);
2528 v3f[ 0] = p->org[0] - right[0] - up[0];
2529 v3f[ 1] = p->org[1] - right[1] - up[1];
2530 v3f[ 2] = p->org[2] - right[2] - up[2];
2531 v3f[ 3] = p->org[0] - right[0] + up[0];
2532 v3f[ 4] = p->org[1] - right[1] + up[1];
2533 v3f[ 5] = p->org[2] - right[2] + up[2];
2534 v3f[ 6] = p->org[0] + right[0] + up[0];
2535 v3f[ 7] = p->org[1] + right[1] + up[1];
2536 v3f[ 8] = p->org[2] + right[2] + up[2];
2537 v3f[ 9] = p->org[0] + right[0] - up[0];
2538 v3f[10] = p->org[1] + right[1] - up[1];
2539 v3f[11] = p->org[2] + right[2] - up[2];
2540 t2f[0] = tex->s1;t2f[1] = tex->t2;
2541 t2f[2] = tex->s1;t2f[3] = tex->t1;
2542 t2f[4] = tex->s2;t2f[5] = tex->t1;
2543 t2f[6] = tex->s2;t2f[7] = tex->t2;
2545 case PARTICLE_SPARK:
2546 len = VectorLength(p->vel);
2547 VectorNormalize2(p->vel, up);
2548 lenfactor = p->stretch * 0.04 * len;
2549 if(lenfactor < size * 0.5)
2550 lenfactor = size * 0.5;
2551 VectorMA(p->org, -lenfactor, up, v);
2552 VectorMA(p->org, lenfactor, up, up2);
2553 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2554 t2f[0] = tex->s1;t2f[1] = tex->t2;
2555 t2f[2] = tex->s1;t2f[3] = tex->t1;
2556 t2f[4] = tex->s2;t2f[5] = tex->t1;
2557 t2f[6] = tex->s2;t2f[7] = tex->t2;
2559 case PARTICLE_VBEAM:
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] = tex->s2;t2f[1] = v[0];
2566 t2f[2] = tex->s1;t2f[3] = v[0];
2567 t2f[4] = tex->s1;t2f[5] = v[1];
2568 t2f[6] = tex->s2;t2f[7] = v[1];
2570 case PARTICLE_HBEAM:
2571 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2572 VectorSubtract(p->vel, p->org, up);
2573 VectorNormalize(up);
2574 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2575 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2576 t2f[0] = v[0];t2f[1] = tex->t1;
2577 t2f[2] = v[0];t2f[3] = tex->t2;
2578 t2f[4] = v[1];t2f[5] = tex->t2;
2579 t2f[6] = v[1];t2f[7] = tex->t1;
2584 // now render batches of particles based on blendmode and texture
2585 blendmode = PBLEND_INVALID;
2589 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2591 p = cl.particles + surfacelist[surfacelistindex];
2593 if (blendmode != p->blendmode)
2595 blendmode = p->blendmode;
2599 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2601 case PBLEND_INVALID:
2603 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2606 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2610 if (texture != particletexture[p->texnum].texture)
2612 texture = particletexture[p->texnum].texture;
2613 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2616 // iterate until we find a change in settings
2617 batchstart = surfacelistindex++;
2618 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2620 p = cl.particles + surfacelist[surfacelistindex];
2621 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2625 batchcount = surfacelistindex - batchstart;
2626 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2630 void R_DrawParticles (void)
2633 int drawparticles = r_drawparticles.integer;
2634 float minparticledist;
2636 float gravity, frametime, f, dist, oldorg[3];
2642 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2643 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2645 // LordHavoc: early out conditions
2646 if (!cl.num_particles)
2649 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2650 gravity = frametime * cl.movevars_gravity;
2651 update = frametime > 0;
2652 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2653 drawdist2 = drawdist2*drawdist2;
2655 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2659 if (cl.free_particle > i)
2660 cl.free_particle = i;
2666 if (p->delayedspawn > cl.time)
2668 p->delayedspawn = 0;
2670 p->size += p->sizeincrease * frametime;
2671 p->alpha -= p->alphafade * frametime;
2673 if (p->alpha <= 0 || p->die <= cl.time)
2676 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2678 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2680 if (p->typeindex == pt_blood)
2681 p->size += frametime * 8;
2683 p->vel[2] -= p->gravity * gravity;
2684 f = 1.0f - min(p->liquidfriction * frametime, 1);
2685 VectorScale(p->vel, f, p->vel);
2689 p->vel[2] -= p->gravity * gravity;
2692 f = 1.0f - min(p->airfriction * frametime, 1);
2693 VectorScale(p->vel, f, p->vel);
2697 VectorCopy(p->org, oldorg);
2698 VectorMA(p->org, frametime, p->vel, p->org);
2699 if (p->bounce && cl.time >= p->delayedcollisions)
2701 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);
2702 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2703 // or if the trace hit something flagged as NOIMPACT
2704 // then remove the particle
2705 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2707 VectorCopy(trace.endpos, p->org);
2708 // react if the particle hit something
2709 if (trace.fraction < 1)
2711 VectorCopy(trace.endpos, p->org);
2713 if (p->staintexnum >= 0)
2715 // blood - splash on solid
2716 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2719 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2720 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2721 if (cl_decals.integer)
2723 // create a decal for the blood splat
2724 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!
2729 if (p->typeindex == pt_blood)
2731 // blood - splash on solid
2732 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2734 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2736 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)));
2737 if (cl_decals.integer)
2739 // create a decal for the blood splat
2740 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);
2745 else if (p->bounce < 0)
2747 // bounce -1 means remove on impact
2752 // anything else - bounce off solid
2753 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2754 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2755 if (DotProduct(p->vel, p->vel) < 0.03)
2756 VectorClear(p->vel);
2762 if (p->typeindex != pt_static)
2764 switch (p->typeindex)
2766 case pt_entityparticle:
2767 // particle that removes itself after one rendered frame
2774 a = CL_PointSuperContents(p->org);
2775 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2779 a = CL_PointSuperContents(p->org);
2780 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2784 a = CL_PointSuperContents(p->org);
2785 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2789 if (cl.time > p->time2)
2792 p->time2 = cl.time + (rand() & 3) * 0.1;
2793 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2794 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2796 a = CL_PointSuperContents(p->org);
2797 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2805 else if (p->delayedspawn)
2809 // don't render particles too close to the view (they chew fillrate)
2810 // also don't render particles behind the view (useless)
2811 // further checks to cull to the frustum would be too slow here
2812 switch(p->typeindex)
2815 // beams have no culling
2816 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2819 if(cl_particles_visculling.integer)
2820 if (!r_refdef.viewcache.world_novis)
2821 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2823 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2825 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2828 // anything else just has to be in front of the viewer and visible at this distance
2829 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2830 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2837 if (cl.free_particle > i)
2838 cl.free_particle = i;
2841 // reduce cl.num_particles if possible
2842 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2845 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2847 particle_t *oldparticles = cl.particles;
2848 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2849 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2850 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2851 Mem_Free(oldparticles);