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 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
33 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
38 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
51 typedef struct particleeffectinfo_s
53 int effectnameindex; // which effect this belongs to
54 // PARTICLEEFFECT_* bits
56 // blood effects may spawn very few particles, so proper fraction-overflow
57 // handling is very important, this variable keeps track of the fraction
58 double particleaccumulator;
59 // the math is: countabsolute + requestedcount * countmultiplier * quality
60 // absolute number of particles to spawn, often used for decals
61 // (unaffected by quality and requestedcount)
63 // multiplier for the number of particles CL_ParticleEffect was told to
64 // spawn, most effects do not really have a count and hence use 1, so
65 // this is often the actual count to spawn, not merely a multiplier
66 float countmultiplier;
67 // if > 0 this causes the particle to spawn in an evenly spaced line from
68 // originmins to originmaxs (causing them to describe a trail, not a box)
70 // type of particle to spawn (defines some aspects of behavior)
72 // blending mode used on this particle type
74 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
75 porientation_t orientation;
76 // range of colors to choose from in hex RRGGBB (like HTML color tags),
77 // randomly interpolated at spawn
78 unsigned int color[2];
79 // a random texture is chosen in this range (note the second value is one
80 // past the last choosable, so for example 8,16 chooses any from 8 up and
82 // if start and end of the range are the same, no randomization is done
84 // range of size values randomly chosen when spawning, plus size increase over time
86 // range of alpha values randomly chosen when spawning, plus alpha fade
88 // how long the particle should live (note it is also removed if alpha drops to 0)
90 // how much gravity affects this particle (negative makes it fly up!)
92 // how much bounce the particle has when it hits a surface
93 // if negative the particle is removed on impact
95 // if in air this friction is applied
96 // if negative the particle accelerates
98 // if in liquid (water/slime/lava) this friction is applied
99 // if negative the particle accelerates
100 float liquidfriction;
101 // these offsets are added to the values given to particleeffect(), and
102 // then an ellipsoid-shaped jitter is added as defined by these
103 // (they are the 3 radii)
105 // stretch velocity factor (used for sparks)
106 float originoffset[3];
107 float velocityoffset[3];
108 float originjitter[3];
109 float velocityjitter[3];
110 float velocitymultiplier;
111 // an effect can also spawn a dlight
112 float lightradiusstart;
113 float lightradiusfade;
116 qboolean lightshadow;
118 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
121 particleeffectinfo_t;
123 #define MAX_PARTICLEEFFECTNAME 256
124 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 #define MAX_PARTICLEEFFECTINFO 4096
128 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
130 static int particlepalette[256];
132 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
133 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
134 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
135 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
136 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
137 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
138 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
139 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
140 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
141 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
142 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
143 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
144 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
145 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
146 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
147 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
148 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
149 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
150 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
151 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
152 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
153 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
154 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
155 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
156 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
157 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
158 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
159 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
160 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
161 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
162 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
163 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
166 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
167 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
168 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
170 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
172 #define MAX_PARTICLETEXTURES 1024
173 // particletexture_t is a rectangle in the particlefonttexture
174 typedef struct particletexture_s
177 float s1, t1, s2, t2;
181 static rtexturepool_t *particletexturepool;
182 static rtexture_t *particlefonttexture;
183 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
185 // texture numbers in particle font
186 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
187 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
188 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
189 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
190 static const int tex_rainsplash = 32;
191 static const int tex_particle = 63;
192 static const int tex_bubble = 62;
193 static const int tex_raindrop = 61;
194 static const int tex_beam = 60;
196 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
197 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
198 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
199 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
200 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
201 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
202 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
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"};
221 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
227 particleeffectinfo_t *info = NULL;
228 const char *text = textstart;
230 effectinfoindex = -1;
231 for (linenumber = 1;;linenumber++)
234 for (arrayindex = 0;arrayindex < 16;arrayindex++)
235 argv[arrayindex][0] = 0;
238 if (!COM_ParseToken_Simple(&text, true, false))
240 if (!strcmp(com_token, "\n"))
244 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
250 #define checkparms(n) if (argc != (n)) {Con_Printf("effectinfo.txt:%i: error while parsing: %s given %i parameters, should be %i parameters\n", linenumber, argv[0], argc, (n));break;}
251 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
252 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
253 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
254 #define readfloat(var) checkparms(2);var = atof(argv[1])
255 if (!strcmp(argv[0], "effect"))
260 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
262 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
265 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
267 if (particleeffectname[effectnameindex][0])
269 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
274 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
278 // if we run out of names, abort
279 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
281 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
284 info = particleeffectinfo + effectinfoindex;
285 info->effectnameindex = effectnameindex;
286 info->particletype = pt_alphastatic;
287 info->blendmode = particletype[info->particletype].blendmode;
288 info->orientation = particletype[info->particletype].orientation;
289 info->tex[0] = tex_particle;
290 info->tex[1] = tex_particle;
291 info->color[0] = 0xFFFFFF;
292 info->color[1] = 0xFFFFFF;
296 info->alpha[1] = 256;
297 info->alpha[2] = 256;
298 info->time[0] = 9999;
299 info->time[1] = 9999;
300 VectorSet(info->lightcolor, 1, 1, 1);
301 info->lightshadow = true;
302 info->lighttime = 9999;
303 info->stretchfactor = 1;
304 info->staincolor[0] = -1;
305 info->staincolor[1] = -1;
306 info->staintex[0] = -1;
307 info->staintex[1] = -1;
309 else if (info == NULL)
311 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
314 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
315 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
316 else if (!strcmp(argv[0], "type"))
319 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
320 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
321 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
322 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
323 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
324 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
325 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
326 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
327 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
328 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
329 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
330 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
331 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
332 info->blendmode = particletype[info->particletype].blendmode;
333 info->orientation = particletype[info->particletype].orientation;
335 else if (!strcmp(argv[0], "blend"))
338 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
339 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
340 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
341 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
343 else if (!strcmp(argv[0], "orientation"))
346 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
347 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
348 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
349 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
350 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
352 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
353 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
354 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
355 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
356 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
357 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
358 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
359 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
360 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
361 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
362 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
363 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
364 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
365 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
366 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
367 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
368 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
369 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
370 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
371 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
372 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
373 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
374 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
375 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
376 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
377 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
378 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
379 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = -1; info->staincolor[1] = -1;}
381 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
390 int CL_ParticleEffectIndexForName(const char *name)
393 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
394 if (!strcmp(particleeffectname[i], name))
399 const char *CL_ParticleEffectNameForIndex(int i)
401 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
403 return particleeffectname[i];
406 // MUST match effectnameindex_t in client.h
407 static const char *standardeffectnames[EFFECT_TOTAL] =
431 "TE_TEI_BIGEXPLOSION",
447 void CL_Particles_LoadEffectInfo(void)
450 unsigned char *filedata;
451 fs_offset_t filesize;
452 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
453 memset(particleeffectname, 0, sizeof(particleeffectname));
454 for (i = 0;i < EFFECT_TOTAL;i++)
455 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
456 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
459 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
469 void CL_ReadPointFile_f (void);
470 void CL_Particles_Init (void)
472 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)");
473 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
475 Cvar_RegisterVariable (&cl_particles);
476 Cvar_RegisterVariable (&cl_particles_quality);
477 Cvar_RegisterVariable (&cl_particles_alpha);
478 Cvar_RegisterVariable (&cl_particles_size);
479 Cvar_RegisterVariable (&cl_particles_quake);
480 Cvar_RegisterVariable (&cl_particles_blood);
481 Cvar_RegisterVariable (&cl_particles_blood_alpha);
482 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
483 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
484 Cvar_RegisterVariable (&cl_particles_explosions_shell);
485 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
486 Cvar_RegisterVariable (&cl_particles_rain);
487 Cvar_RegisterVariable (&cl_particles_snow);
488 Cvar_RegisterVariable (&cl_particles_smoke);
489 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
490 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
491 Cvar_RegisterVariable (&cl_particles_sparks);
492 Cvar_RegisterVariable (&cl_particles_bubbles);
493 Cvar_RegisterVariable (&cl_particles_visculling);
494 Cvar_RegisterVariable (&cl_decals);
495 Cvar_RegisterVariable (&cl_decals_visculling);
496 Cvar_RegisterVariable (&cl_decals_time);
497 Cvar_RegisterVariable (&cl_decals_fadetime);
500 void CL_Particles_Shutdown (void)
504 // list of all 26 parameters:
505 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
506 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
507 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
508 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
509 // palpha - opacity of particle as 0-255 (can be more than 255)
510 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
511 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
512 // pgravity - how much effect gravity has on the particle (0-1)
513 // 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
514 // px,py,pz - starting origin of particle
515 // pvx,pvy,pvz - starting velocity of particle
516 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
517 // blendmode - one of the PBLEND_ values
518 // orientation - one of the PARTICLE_ values
519 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
520 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
521 static particle_t *CL_NewParticle(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)
526 if (!cl_particles.integer)
528 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
529 if (cl.free_particle >= cl.max_particles)
532 lifetime = palpha / min(1, palphafade);
533 part = &cl.particles[cl.free_particle++];
534 if (cl.num_particles < cl.free_particle)
535 cl.num_particles = cl.free_particle;
536 memset(part, 0, sizeof(*part));
537 part->typeindex = ptypeindex;
538 part->blendmode = blendmode;
539 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
541 particletexture_t *tex = &particletexture[ptex];
542 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
543 part->orientation = PARTICLE_VBEAM;
545 part->orientation = PARTICLE_HBEAM;
548 part->orientation = orientation;
549 l2 = (int)lhrandom(0.5, 256.5);
551 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
552 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
553 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
554 part->staintexnum = staintex;
555 if(staincolor1 >= 0 && staincolor2 >= 0)
557 l2 = (int)lhrandom(0.5, 256.5);
559 if(blendmode == PBLEND_INVMOD)
561 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
562 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
563 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
567 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
568 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
569 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
571 if(r > 0xFF) r = 0xFF;
572 if(g > 0xFF) g = 0xFF;
573 if(b > 0xFF) b = 0xFF;
577 r = part->color[0]; // -1 is shorthand for stain = particle color
581 part->staincolor = r * 65536 + g * 256 + b;
584 part->sizeincrease = psizeincrease;
585 part->alpha = palpha;
586 part->alphafade = palphafade;
587 part->gravity = pgravity;
588 part->bounce = pbounce;
589 part->stretch = stretch;
591 part->org[0] = px + originjitter * v[0];
592 part->org[1] = py + originjitter * v[1];
593 part->org[2] = pz + originjitter * v[2];
594 part->vel[0] = pvx + velocityjitter * v[0];
595 part->vel[1] = pvy + velocityjitter * v[1];
596 part->vel[2] = pvz + velocityjitter * v[2];
598 part->airfriction = pairfriction;
599 part->liquidfriction = pliquidfriction;
600 part->die = cl.time + lifetime;
601 part->delayedcollisions = 0;
602 part->qualityreduction = pqualityreduction;
603 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
604 if (part->typeindex == pt_rain)
608 float lifetime = part->die - cl.time;
611 // turn raindrop into simple spark and create delayedspawn splash effect
612 part->typeindex = pt_spark;
614 VectorMA(part->org, lifetime, part->vel, endvec);
615 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
616 part->die = cl.time + lifetime * trace.fraction;
617 part2 = CL_NewParticle(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);
620 part2->delayedspawn = part->die;
621 part2->die += part->die - cl.time;
622 for (i = rand() & 7;i < 10;i++)
624 part2 = CL_NewParticle(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);
627 part2->delayedspawn = part->die;
628 part2->die += part->die - cl.time;
633 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
635 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
638 VectorMA(part->org, lifetime, part->vel, endvec);
639 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
640 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
645 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
649 if (!cl_decals.integer)
651 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
652 if (cl.free_decal >= cl.max_decals)
654 decal = &cl.decals[cl.free_decal++];
655 if (cl.num_decals < cl.free_decal)
656 cl.num_decals = cl.free_decal;
657 memset(decal, 0, sizeof(*decal));
658 decal->typeindex = pt_decal;
659 decal->texnum = texnum;
660 VectorAdd(org, normal, decal->org);
661 VectorCopy(normal, decal->normal);
663 decal->alpha = alpha;
664 decal->time2 = cl.time;
665 l2 = (int)lhrandom(0.5, 256.5);
667 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
668 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
669 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
670 decal->owner = hitent;
671 decal->clusterindex = -1000; // no vis culling unless we're sure
674 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
675 decal->ownermodel = cl.entities[decal->owner].render.model;
676 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
677 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
681 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
683 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
685 decal->clusterindex = leaf->clusterindex;
690 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
693 float bestfrac, bestorg[3], bestnormal[3];
695 int besthitent = 0, hitent;
698 for (i = 0;i < 32;i++)
701 VectorMA(org, maxdist, org2, org2);
702 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
703 // take the closest trace result that doesn't end up hitting a NOMARKS
704 // surface (sky for example)
705 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
707 bestfrac = trace.fraction;
709 VectorCopy(trace.endpos, bestorg);
710 VectorCopy(trace.plane.normal, bestnormal);
714 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
717 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
718 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
719 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)
722 matrix4x4_t tempmatrix;
723 VectorLerp(originmins, 0.5, originmaxs, center);
724 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
725 if (effectnameindex == EFFECT_SVC_PARTICLE)
727 if (cl_particles.integer)
729 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
731 CL_ParticleExplosion(center);
732 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
733 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
736 count *= cl_particles_quality.value;
737 for (;count > 0;count--)
739 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
740 CL_NewParticle(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);
745 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
746 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
747 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
748 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
749 else if (effectnameindex == EFFECT_TE_SPIKE)
751 if (cl_particles_bulletimpacts.integer)
753 if (cl_particles_quake.integer)
755 if (cl_particles_smoke.integer)
756 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
760 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
761 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
762 CL_NewParticle(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);
766 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
767 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
769 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
771 if (cl_particles_bulletimpacts.integer)
773 if (cl_particles_quake.integer)
775 if (cl_particles_smoke.integer)
776 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
780 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
781 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
782 CL_NewParticle(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);
786 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
787 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
788 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);
790 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
792 if (cl_particles_bulletimpacts.integer)
794 if (cl_particles_quake.integer)
796 if (cl_particles_smoke.integer)
797 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
801 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
802 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
803 CL_NewParticle(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);
807 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
808 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
810 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
812 if (cl_particles_bulletimpacts.integer)
814 if (cl_particles_quake.integer)
816 if (cl_particles_smoke.integer)
817 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
821 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
822 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
823 CL_NewParticle(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);
827 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
828 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
829 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);
831 else if (effectnameindex == EFFECT_TE_BLOOD)
833 if (!cl_particles_blood.integer)
835 if (cl_particles_quake.integer)
836 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
839 static double bloodaccumulator = 0;
840 //CL_NewParticle(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);
841 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
842 for (;bloodaccumulator > 0;bloodaccumulator--)
843 CL_NewParticle(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);
846 else if (effectnameindex == EFFECT_TE_SPARK)
847 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
848 else if (effectnameindex == EFFECT_TE_PLASMABURN)
850 // plasma scorch mark
851 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
852 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
853 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
855 else if (effectnameindex == EFFECT_TE_GUNSHOT)
857 if (cl_particles_bulletimpacts.integer)
859 if (cl_particles_quake.integer)
860 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
863 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
864 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
865 CL_NewParticle(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);
869 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
870 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
872 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
874 if (cl_particles_bulletimpacts.integer)
876 if (cl_particles_quake.integer)
877 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
880 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
881 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
882 CL_NewParticle(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);
886 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
887 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
888 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);
890 else if (effectnameindex == EFFECT_TE_EXPLOSION)
892 CL_ParticleExplosion(center);
893 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);
895 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
897 CL_ParticleExplosion(center);
898 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);
900 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
902 if (cl_particles_quake.integer)
905 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
908 CL_NewParticle(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);
910 CL_NewParticle(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);
914 CL_ParticleExplosion(center);
915 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);
917 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
918 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);
919 else if (effectnameindex == EFFECT_TE_FLAMEJET)
921 count *= cl_particles_quality.value;
923 CL_NewParticle(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);
925 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
927 float i, j, inc, vel;
930 inc = 8 / cl_particles_quality.value;
931 for (i = -128;i < 128;i += inc)
933 for (j = -128;j < 128;j += inc)
935 dir[0] = j + lhrandom(0, inc);
936 dir[1] = i + lhrandom(0, inc);
938 org[0] = center[0] + dir[0];
939 org[1] = center[1] + dir[1];
940 org[2] = center[2] + lhrandom(0, 64);
941 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
942 CL_NewParticle(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);
946 else if (effectnameindex == EFFECT_TE_TELEPORT)
948 float i, j, k, inc, vel;
951 if (cl_particles_quake.integer)
952 inc = 4 / cl_particles_quality.value;
954 inc = 8 / cl_particles_quality.value;
955 for (i = -16;i < 16;i += inc)
957 for (j = -16;j < 16;j += inc)
959 for (k = -24;k < 32;k += inc)
961 VectorSet(dir, i*8, j*8, k*8);
962 VectorNormalize(dir);
963 vel = lhrandom(50, 113);
964 if (cl_particles_quake.integer)
965 CL_NewParticle(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);
967 CL_NewParticle(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);
971 if (!cl_particles_quake.integer)
972 CL_NewParticle(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);
973 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);
975 else if (effectnameindex == EFFECT_TE_TEI_G3)
976 CL_NewParticle(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);
977 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
979 if (cl_particles_smoke.integer)
981 count *= 0.25f * cl_particles_quality.value;
983 CL_NewParticle(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);
986 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
988 CL_ParticleExplosion(center);
989 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);
991 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
994 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
995 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
996 if (cl_particles_smoke.integer)
997 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
998 CL_NewParticle(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);
999 if (cl_particles_sparks.integer)
1000 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1001 CL_NewParticle(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);
1002 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);
1004 else if (effectnameindex == EFFECT_EF_FLAME)
1006 count *= 300 * cl_particles_quality.value;
1008 CL_NewParticle(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);
1009 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);
1011 else if (effectnameindex == EFFECT_EF_STARDUST)
1013 count *= 200 * cl_particles_quality.value;
1015 CL_NewParticle(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);
1016 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);
1018 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1022 int smoke, blood, bubbles, r, color;
1024 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1027 Vector4Set(light, 0, 0, 0, 0);
1029 if (effectnameindex == EFFECT_TR_ROCKET)
1030 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1031 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1033 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1034 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1036 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1038 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1039 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1043 matrix4x4_t tempmatrix;
1044 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1045 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);
1046 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1050 if (!spawnparticles)
1053 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1056 VectorSubtract(originmaxs, originmins, dir);
1057 len = VectorNormalizeLength(dir);
1060 dec = -ent->persistent.trail_time;
1061 ent->persistent.trail_time += len;
1062 if (ent->persistent.trail_time < 0.01f)
1065 // if we skip out, leave it reset
1066 ent->persistent.trail_time = 0.0f;
1071 // advance into this frame to reach the first puff location
1072 VectorMA(originmins, dec, dir, pos);
1075 smoke = cl_particles.integer && cl_particles_smoke.integer;
1076 blood = cl_particles.integer && cl_particles_blood.integer;
1077 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1078 qd = 1.0f / cl_particles_quality.value;
1085 if (effectnameindex == EFFECT_TR_BLOOD)
1087 if (cl_particles_quake.integer)
1089 color = particlepalette[67 + (rand()&3)];
1090 CL_NewParticle(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);
1095 CL_NewParticle(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);
1098 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1100 if (cl_particles_quake.integer)
1103 color = particlepalette[67 + (rand()&3)];
1104 CL_NewParticle(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);
1109 CL_NewParticle(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);
1115 if (effectnameindex == EFFECT_TR_ROCKET)
1117 if (cl_particles_quake.integer)
1120 color = particlepalette[ramp3[r]];
1121 CL_NewParticle(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);
1125 CL_NewParticle(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);
1126 CL_NewParticle(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);
1129 else if (effectnameindex == EFFECT_TR_GRENADE)
1131 if (cl_particles_quake.integer)
1134 color = particlepalette[ramp3[r]];
1135 CL_NewParticle(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);
1139 CL_NewParticle(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);
1142 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1144 if (cl_particles_quake.integer)
1147 color = particlepalette[52 + (rand()&7)];
1148 CL_NewParticle(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);
1149 CL_NewParticle(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);
1151 else if (gamemode == GAME_GOODVSBAD2)
1154 CL_NewParticle(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);
1158 color = particlepalette[20 + (rand()&7)];
1159 CL_NewParticle(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);
1162 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1164 if (cl_particles_quake.integer)
1167 color = particlepalette[230 + (rand()&7)];
1168 CL_NewParticle(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);
1169 CL_NewParticle(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);
1173 color = particlepalette[226 + (rand()&7)];
1174 CL_NewParticle(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);
1177 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1179 if (cl_particles_quake.integer)
1181 color = particlepalette[152 + (rand()&3)];
1182 CL_NewParticle(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);
1184 else if (gamemode == GAME_GOODVSBAD2)
1187 CL_NewParticle(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);
1189 else if (gamemode == GAME_PRYDON)
1192 CL_NewParticle(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);
1195 CL_NewParticle(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);
1197 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1200 CL_NewParticle(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);
1202 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1205 CL_NewParticle(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);
1207 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1208 CL_NewParticle(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);
1212 if (effectnameindex == EFFECT_TR_ROCKET)
1213 CL_NewParticle(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);
1214 else if (effectnameindex == EFFECT_TR_GRENADE)
1215 CL_NewParticle(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);
1217 // advance to next time and position
1220 VectorMA (pos, dec, dir, pos);
1223 ent->persistent.trail_time = len;
1225 else if (developer.integer >= 1)
1226 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1229 // this is also called on point effects with spawndlight = true and
1230 // spawnparticles = true
1231 // it is called CL_ParticleTrail because most code does not want to supply
1232 // these parameters, only trail handling does
1233 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)
1236 qboolean found = false;
1237 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1239 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1240 return; // no such effect
1242 VectorLerp(originmins, 0.5, originmaxs, center);
1243 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1245 int effectinfoindex;
1248 particleeffectinfo_t *info;
1250 vec3_t centervelocity;
1256 qboolean underwater;
1257 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1258 VectorLerp(originmins, 0.5, originmaxs, center);
1259 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1260 supercontents = CL_PointSuperContents(center);
1261 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1262 VectorSubtract(originmaxs, originmins, traildir);
1263 traillen = VectorLength(traildir);
1264 VectorNormalize(traildir);
1265 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1267 if (info->effectnameindex == effectnameindex)
1270 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1272 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1275 // spawn a dlight if requested
1276 if (info->lightradiusstart > 0 && spawndlight)
1278 matrix4x4_t tempmatrix;
1279 if (info->trailspacing > 0)
1280 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1282 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1283 if (info->lighttime > 0 && info->lightradiusfade > 0)
1285 // light flash (explosion, etc)
1286 // called when effect starts
1287 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);
1292 // called by CL_LinkNetworkEntity
1293 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1294 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);
1295 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1299 if (!spawnparticles)
1304 if (info->tex[1] > info->tex[0])
1306 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1307 tex = min(tex, info->tex[1] - 1);
1309 if(info->staintex[0] < 0)
1310 staintex = info->staintex[0];
1313 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1314 staintex = min(staintex, info->staintex[1] - 1);
1316 if (info->particletype == pt_decal)
1317 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]);
1318 else if (info->orientation == PARTICLE_HBEAM)
1319 CL_NewParticle(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);
1322 if (!cl_particles.integer)
1324 switch (info->particletype)
1326 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1327 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1328 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1329 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1330 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1331 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1334 VectorCopy(originmins, trailpos);
1335 if (info->trailspacing > 0)
1337 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1338 trailstep = info->trailspacing / cl_particles_quality.value;
1342 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1345 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1346 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1348 if (info->tex[1] > info->tex[0])
1350 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1351 tex = min(tex, info->tex[1] - 1);
1355 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1356 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1357 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1360 CL_NewParticle(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);
1362 VectorMA(trailpos, trailstep, traildir, trailpos);
1369 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1372 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)
1374 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1382 void CL_EntityParticles (const entity_t *ent)
1385 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1386 static vec3_t avelocities[NUMVERTEXNORMALS];
1387 if (!cl_particles.integer) return;
1388 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1390 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1392 if (!avelocities[0][0])
1393 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1394 avelocities[0][i] = lhrandom(0, 2.55);
1396 for (i = 0;i < NUMVERTEXNORMALS;i++)
1398 yaw = cl.time * avelocities[i][0];
1399 pitch = cl.time * avelocities[i][1];
1400 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1401 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1402 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1403 CL_NewParticle(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);
1408 void CL_ReadPointFile_f (void)
1410 vec3_t org, leakorg;
1412 char *pointfile = NULL, *pointfilepos, *t, tchar;
1413 char name[MAX_OSPATH];
1418 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1419 strlcat (name, ".pts", sizeof (name));
1420 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1423 Con_Printf("Could not open %s\n", name);
1427 Con_Printf("Reading %s...\n", name);
1428 VectorClear(leakorg);
1431 pointfilepos = pointfile;
1432 while (*pointfilepos)
1434 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1439 while (*t && *t != '\n' && *t != '\r')
1443 #if _MSC_VER >= 1400
1444 #define sscanf sscanf_s
1446 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1452 VectorCopy(org, leakorg);
1455 if (cl.num_particles < cl.max_particles - 3)
1458 CL_NewParticle(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);
1461 Mem_Free(pointfile);
1462 VectorCopy(leakorg, org);
1463 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1465 CL_NewParticle(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);
1466 CL_NewParticle(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);
1467 CL_NewParticle(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);
1472 CL_ParseParticleEffect
1474 Parse an effect out of the server message
1477 void CL_ParseParticleEffect (void)
1480 int i, count, msgcount, color;
1482 MSG_ReadVector(org, cls.protocol);
1483 for (i=0 ; i<3 ; i++)
1484 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1485 msgcount = MSG_ReadByte ();
1486 color = MSG_ReadByte ();
1488 if (msgcount == 255)
1493 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1498 CL_ParticleExplosion
1502 void CL_ParticleExplosion (const vec3_t org)
1508 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1509 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1511 if (cl_particles_quake.integer)
1513 for (i = 0;i < 1024;i++)
1519 color = particlepalette[ramp1[r]];
1520 CL_NewParticle(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);
1524 color = particlepalette[ramp2[r]];
1525 CL_NewParticle(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);
1531 i = CL_PointSuperContents(org);
1532 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1534 if (cl_particles.integer && cl_particles_bubbles.integer)
1535 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1536 CL_NewParticle(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);
1540 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1542 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1546 for (k = 0;k < 16;k++)
1549 VectorMA(org, 128, v2, v);
1550 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1551 if (trace.fraction >= 0.1)
1554 VectorSubtract(trace.endpos, org, v2);
1555 VectorScale(v2, 2.0f, v2);
1556 CL_NewParticle(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);
1562 if (cl_particles_explosions_shell.integer)
1563 R_NewExplosion(org);
1568 CL_ParticleExplosion2
1572 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1575 if (!cl_particles.integer) return;
1577 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1579 k = particlepalette[colorStart + (i % colorLength)];
1580 if (cl_particles_quake.integer)
1581 CL_NewParticle(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);
1583 CL_NewParticle(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);
1587 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1589 if (cl_particles_sparks.integer)
1591 sparkcount *= cl_particles_quality.value;
1592 while(sparkcount-- > 0)
1593 CL_NewParticle(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);
1597 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1599 if (cl_particles_smoke.integer)
1601 smokecount *= cl_particles_quality.value;
1602 while(smokecount-- > 0)
1603 CL_NewParticle(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);
1607 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)
1610 if (!cl_particles.integer) return;
1612 count = (int)(count * cl_particles_quality.value);
1615 k = particlepalette[colorbase + (rand()&3)];
1616 CL_NewParticle(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);
1620 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1623 float minz, maxz, lifetime = 30;
1624 if (!cl_particles.integer) return;
1625 if (dir[2] < 0) // falling
1627 minz = maxs[2] + dir[2] * 0.1;
1630 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1635 maxz = maxs[2] + dir[2] * 0.1;
1637 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1640 count = (int)(count * cl_particles_quality.value);
1645 if (!cl_particles_rain.integer) break;
1646 count *= 4; // ick, this should be in the mod or maps?
1650 k = particlepalette[colorbase + (rand()&3)];
1651 if (gamemode == GAME_GOODVSBAD2)
1652 CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1654 CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1658 if (!cl_particles_snow.integer) break;
1661 k = particlepalette[colorbase + (rand()&3)];
1662 if (gamemode == GAME_GOODVSBAD2)
1663 CL_NewParticle(pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1665 CL_NewParticle(pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1669 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1673 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1674 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1675 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1676 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1678 #define PARTICLETEXTURESIZE 64
1679 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1681 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1685 dz = 1 - (dx*dx+dy*dy);
1686 if (dz > 0) // it does hit the sphere
1690 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1691 VectorNormalize(normal);
1692 dot = DotProduct(normal, light);
1693 if (dot > 0.5) // interior reflection
1694 f += ((dot * 2) - 1);
1695 else if (dot < -0.5) // exterior reflection
1696 f += ((dot * -2) - 1);
1698 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1699 VectorNormalize(normal);
1700 dot = DotProduct(normal, light);
1701 if (dot > 0.5) // interior reflection
1702 f += ((dot * 2) - 1);
1703 else if (dot < -0.5) // exterior reflection
1704 f += ((dot * -2) - 1);
1706 f += 16; // just to give it a haze so you can see the outline
1707 f = bound(0, f, 255);
1708 return (unsigned char) f;
1714 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1715 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1717 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1718 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1719 *width = particlefontcellwidth;
1720 *height = particlefontcellheight;
1723 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1725 int basex, basey, w, h, y;
1726 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1727 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1728 Sys_Error("invalid particle texture size for autogenerating");
1729 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1730 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1733 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1736 float cx, cy, dx, dy, f, iradius;
1738 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1739 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1740 iradius = 1.0f / radius;
1741 alpha *= (1.0f / 255.0f);
1742 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1744 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1748 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1753 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1754 d[0] += (int)(f * (blue - d[0]));
1755 d[1] += (int)(f * (green - d[1]));
1756 d[2] += (int)(f * (red - d[2]));
1762 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1765 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1767 data[0] = bound(minb, data[0], maxb);
1768 data[1] = bound(ming, data[1], maxg);
1769 data[2] = bound(minr, data[2], maxr);
1773 void particletextureinvert(unsigned char *data)
1776 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1778 data[0] = 255 - data[0];
1779 data[1] = 255 - data[1];
1780 data[2] = 255 - data[2];
1784 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1785 static void R_InitBloodTextures (unsigned char *particletexturedata)
1788 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1791 for (i = 0;i < 8;i++)
1793 memset(&data[0][0][0], 255, sizeof(data));
1794 for (k = 0;k < 24;k++)
1795 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1796 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1797 particletextureinvert(&data[0][0][0]);
1798 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1802 for (i = 0;i < 8;i++)
1804 memset(&data[0][0][0], 255, sizeof(data));
1806 for (j = 1;j < 10;j++)
1807 for (k = min(j, m - 1);k < m;k++)
1808 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1809 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1810 particletextureinvert(&data[0][0][0]);
1811 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1816 //uncomment this to make engine save out particle font to a tga file when run
1817 //#define DUMPPARTICLEFONT
1819 static void R_InitParticleTexture (void)
1821 int x, y, d, i, k, m;
1822 int basex, basey, w, h;
1826 fs_offset_t filesize;
1828 // a note: decals need to modulate (multiply) the background color to
1829 // properly darken it (stain), and they need to be able to alpha fade,
1830 // this is a very difficult challenge because it means fading to white
1831 // (no change to background) rather than black (darkening everything
1832 // behind the whole decal polygon), and to accomplish this the texture is
1833 // inverted (dark red blood on white background becomes brilliant cyan
1834 // and white on black background) so we can alpha fade it to black, then
1835 // we invert it again during the blendfunc to make it work...
1837 #ifndef DUMPPARTICLEFONT
1838 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1839 if (particlefonttexture)
1841 // TODO maybe allow custom grid size?
1842 particlefontwidth = image_width;
1843 particlefontheight = image_height;
1844 particlefontcellwidth = image_width / 8;
1845 particlefontcellheight = image_height / 8;
1846 particlefontcols = 8;
1847 particlefontrows = 8;
1852 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1853 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1855 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1856 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1857 particlefontcols = 8;
1858 particlefontrows = 8;
1860 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1863 for (i = 0;i < 8;i++)
1865 memset(&data[0][0][0], 255, sizeof(data));
1868 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1870 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1871 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1873 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1875 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1876 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1878 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1879 d = (noise2[y][x] - 128) * 3 + 192;
1881 d = (int)(d * (1-(dx*dx+dy*dy)));
1882 d = (d * noise1[y][x]) >> 7;
1883 d = bound(0, d, 255);
1884 data[y][x][3] = (unsigned char) d;
1891 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1895 memset(&data[0][0][0], 255, sizeof(data));
1896 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1898 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1899 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1901 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1902 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1903 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1906 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1909 memset(&data[0][0][0], 255, sizeof(data));
1910 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1912 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1913 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1915 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1916 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1917 d = bound(0, d, 255);
1918 data[y][x][3] = (unsigned char) d;
1921 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1924 memset(&data[0][0][0], 255, sizeof(data));
1925 light[0] = 1;light[1] = 1;light[2] = 1;
1926 VectorNormalize(light);
1927 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1929 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1930 // stretch upper half of bubble by +50% and shrink lower half by -50%
1931 // (this gives an elongated teardrop shape)
1933 dy = (dy - 0.5f) * 2.0f;
1935 dy = (dy - 0.5f) / 1.5f;
1936 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1938 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1939 // shrink bubble width to half
1941 data[y][x][3] = shadebubble(dx, dy, light);
1944 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1947 memset(&data[0][0][0], 255, sizeof(data));
1948 light[0] = 1;light[1] = 1;light[2] = 1;
1949 VectorNormalize(light);
1950 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1952 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1953 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1955 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1956 data[y][x][3] = shadebubble(dx, dy, light);
1959 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1961 // Blood particles and blood decals
1962 R_InitBloodTextures (particletexturedata);
1965 for (i = 0;i < 8;i++)
1967 memset(&data[0][0][0], 255, sizeof(data));
1968 for (k = 0;k < 12;k++)
1969 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1970 for (k = 0;k < 3;k++)
1971 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1972 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1973 particletextureinvert(&data[0][0][0]);
1974 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1977 #ifdef DUMPPARTICLEFONT
1978 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1981 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1983 Mem_Free(particletexturedata);
1985 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1987 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
1988 particletexture[i].texture = particlefonttexture;
1989 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
1990 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
1991 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
1992 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
1995 #ifndef DUMPPARTICLEFONT
1996 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1997 if (!particletexture[tex_beam].texture)
2000 unsigned char noise3[64][64], data2[64][16][4];
2002 fractalnoise(&noise3[0][0], 64, 4);
2004 for (y = 0;y < 64;y++)
2006 dy = (y - 0.5f*64) / (64*0.5f-1);
2007 for (x = 0;x < 16;x++)
2009 dx = (x - 0.5f*16) / (16*0.5f-2);
2010 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2011 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2012 data2[y][x][3] = 255;
2016 #ifdef DUMPPARTICLEFONT
2017 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2019 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
2021 particletexture[tex_beam].s1 = 0;
2022 particletexture[tex_beam].t1 = 0;
2023 particletexture[tex_beam].s2 = 1;
2024 particletexture[tex_beam].t2 = 1;
2026 // now load an texcoord/texture override file
2027 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2034 if(!COM_ParseToken_Simple(&bufptr, true, false))
2036 if(!strcmp(com_token, "\n"))
2037 continue; // empty line
2038 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2039 particletexture[i].texture = particlefonttexture;
2041 if (!COM_ParseToken_Simple(&bufptr, true, false))
2043 if (!strcmp(com_token, "\n"))
2045 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2048 particletexture[i].s1 = atof(com_token);
2050 if (!COM_ParseToken_Simple(&bufptr, true, false))
2052 if (!strcmp(com_token, "\n"))
2054 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2057 particletexture[i].t1 = atof(com_token);
2059 if (!COM_ParseToken_Simple(&bufptr, true, false))
2061 if (!strcmp(com_token, "\n"))
2063 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2066 particletexture[i].s2 = atof(com_token);
2068 if (!COM_ParseToken_Simple(&bufptr, true, false))
2070 if (!strcmp(com_token, "\n"))
2072 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2075 particletexture[i].t2 = atof(com_token);
2081 static void r_part_start(void)
2084 // generate particlepalette for convenience from the main one
2085 for (i = 0;i < 256;i++)
2086 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2087 particletexturepool = R_AllocTexturePool();
2088 R_InitParticleTexture ();
2089 CL_Particles_LoadEffectInfo();
2092 static void r_part_shutdown(void)
2094 R_FreeTexturePool(&particletexturepool);
2097 static void r_part_newmap(void)
2099 CL_Particles_LoadEffectInfo();
2102 #define BATCHSIZE 256
2103 unsigned short particle_elements[BATCHSIZE*6];
2105 void R_Particles_Init (void)
2108 for (i = 0;i < BATCHSIZE;i++)
2110 particle_elements[i*6+0] = i*4+0;
2111 particle_elements[i*6+1] = i*4+1;
2112 particle_elements[i*6+2] = i*4+2;
2113 particle_elements[i*6+3] = i*4+0;
2114 particle_elements[i*6+4] = i*4+2;
2115 particle_elements[i*6+5] = i*4+3;
2118 Cvar_RegisterVariable(&r_drawparticles);
2119 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2120 Cvar_RegisterVariable(&r_drawdecals);
2121 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2122 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2125 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2127 int surfacelistindex;
2129 float *v3f, *t2f, *c4f;
2130 particletexture_t *tex;
2131 float right[3], up[3], size, ca;
2132 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
2133 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2135 r_refdef.stats.decals += numsurfaces;
2136 R_Mesh_Matrix(&identitymatrix);
2137 R_Mesh_ResetTextureState();
2138 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2139 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2140 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2141 R_SetupGenericShader(true);
2142 GL_DepthMask(false);
2143 GL_DepthRange(0, 1);
2144 GL_PolygonOffset(0, 0);
2146 GL_CullFace(GL_NONE);
2148 // generate all the vertices at once
2149 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2151 d = cl.decals + surfacelist[surfacelistindex];
2154 c4f = particle_color4f + 16*surfacelistindex;
2155 ca = d->alpha * alphascale;
2156 if (r_refdef.fogenabled)
2157 ca *= FogPoint_World(d->org);
2158 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2159 Vector4Copy(c4f, c4f + 4);
2160 Vector4Copy(c4f, c4f + 8);
2161 Vector4Copy(c4f, c4f + 12);
2163 // calculate vertex positions
2164 size = d->size * cl_particles_size.value;
2165 VectorVectors(d->normal, right, up);
2166 VectorScale(right, size, right);
2167 VectorScale(up, size, up);
2168 v3f = particle_vertex3f + 12*surfacelistindex;
2169 v3f[ 0] = d->org[0] - right[0] - up[0];
2170 v3f[ 1] = d->org[1] - right[1] - up[1];
2171 v3f[ 2] = d->org[2] - right[2] - up[2];
2172 v3f[ 3] = d->org[0] - right[0] + up[0];
2173 v3f[ 4] = d->org[1] - right[1] + up[1];
2174 v3f[ 5] = d->org[2] - right[2] + up[2];
2175 v3f[ 6] = d->org[0] + right[0] + up[0];
2176 v3f[ 7] = d->org[1] + right[1] + up[1];
2177 v3f[ 8] = d->org[2] + right[2] + up[2];
2178 v3f[ 9] = d->org[0] + right[0] - up[0];
2179 v3f[10] = d->org[1] + right[1] - up[1];
2180 v3f[11] = d->org[2] + right[2] - up[2];
2182 // calculate texcoords
2183 tex = &particletexture[d->texnum];
2184 t2f = particle_texcoord2f + 8*surfacelistindex;
2185 t2f[0] = tex->s1;t2f[1] = tex->t2;
2186 t2f[2] = tex->s1;t2f[3] = tex->t1;
2187 t2f[4] = tex->s2;t2f[5] = tex->t1;
2188 t2f[6] = tex->s2;t2f[7] = tex->t2;
2191 // now render the decals all at once
2192 // (this assumes they all use one particle font texture!)
2193 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2194 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2195 GL_LockArrays(0, numsurfaces*4);
2196 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2197 GL_LockArrays(0, 0);
2200 void R_DrawDecals (void)
2208 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2209 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2211 // LordHavoc: early out conditions
2212 if ((!cl.num_decals) || (!r_drawdecals.integer))
2215 decalfade = frametime * 256 / cl_decals_fadetime.value;
2216 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2217 drawdist2 = drawdist2*drawdist2;
2219 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2221 if (!decal->typeindex)
2224 if (cl.time > decal->time2 + cl_decals_time.value)
2226 decal->alpha -= decalfade;
2227 if (decal->alpha <= 0)
2233 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2235 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2236 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2242 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2245 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))
2246 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2249 decal->typeindex = 0;
2250 if (cl.free_decal > i)
2254 // reduce cl.num_decals if possible
2255 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2258 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2260 decal_t *olddecals = cl.decals;
2261 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2262 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2263 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2264 Mem_Free(olddecals);
2268 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2270 int surfacelistindex;
2271 int batchstart, batchcount;
2272 const particle_t *p;
2274 rtexture_t *texture;
2275 float *v3f, *t2f, *c4f;
2276 particletexture_t *tex;
2277 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2278 float ambient[3], diffuse[3], diffusenormal[3];
2279 vec4_t colormultiplier;
2280 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2282 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));
2284 r_refdef.stats.particles += numsurfaces;
2285 R_Mesh_Matrix(&identitymatrix);
2286 R_Mesh_ResetTextureState();
2287 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2288 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2289 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2290 R_SetupGenericShader(true);
2291 GL_DepthMask(false);
2292 GL_DepthRange(0, 1);
2293 GL_PolygonOffset(0, 0);
2295 GL_CullFace(GL_NONE);
2297 // first generate all the vertices at once
2298 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2300 p = cl.particles + surfacelist[surfacelistindex];
2302 blendmode = p->blendmode;
2304 c4f[0] = p->color[0] * colormultiplier[0];
2305 c4f[1] = p->color[1] * colormultiplier[1];
2306 c4f[2] = p->color[2] * colormultiplier[2];
2307 c4f[3] = p->alpha * colormultiplier[3];
2310 case PBLEND_INVALID:
2313 // additive and modulate can just fade out in fog (this is correct)
2314 if (r_refdef.fogenabled)
2315 c4f[3] *= FogPoint_World(p->org);
2316 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2323 // note: lighting is not cheap!
2324 if (particletype[p->typeindex].lighting)
2326 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2327 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2328 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2329 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2331 // mix in the fog color
2332 if (r_refdef.fogenabled)
2334 fog = FogPoint_World(p->org);
2336 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2337 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2338 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2342 // copy the color into the other three vertices
2343 Vector4Copy(c4f, c4f + 4);
2344 Vector4Copy(c4f, c4f + 8);
2345 Vector4Copy(c4f, c4f + 12);
2347 size = p->size * cl_particles_size.value;
2348 tex = &particletexture[p->texnum];
2349 switch(p->orientation)
2351 case PARTICLE_INVALID:
2352 case PARTICLE_BILLBOARD:
2353 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2354 VectorScale(r_refdef.view.up, size, up);
2355 v3f[ 0] = p->org[0] - right[0] - up[0];
2356 v3f[ 1] = p->org[1] - right[1] - up[1];
2357 v3f[ 2] = p->org[2] - right[2] - up[2];
2358 v3f[ 3] = p->org[0] - right[0] + up[0];
2359 v3f[ 4] = p->org[1] - right[1] + up[1];
2360 v3f[ 5] = p->org[2] - right[2] + up[2];
2361 v3f[ 6] = p->org[0] + right[0] + up[0];
2362 v3f[ 7] = p->org[1] + right[1] + up[1];
2363 v3f[ 8] = p->org[2] + right[2] + up[2];
2364 v3f[ 9] = p->org[0] + right[0] - up[0];
2365 v3f[10] = p->org[1] + right[1] - up[1];
2366 v3f[11] = p->org[2] + right[2] - up[2];
2367 t2f[0] = tex->s1;t2f[1] = tex->t2;
2368 t2f[2] = tex->s1;t2f[3] = tex->t1;
2369 t2f[4] = tex->s2;t2f[5] = tex->t1;
2370 t2f[6] = tex->s2;t2f[7] = tex->t2;
2372 case PARTICLE_ORIENTED_DOUBLESIDED:
2373 VectorVectors(p->vel, right, up);
2374 VectorScale(right, size * p->stretch, right);
2375 VectorScale(up, size, up);
2376 v3f[ 0] = p->org[0] - right[0] - up[0];
2377 v3f[ 1] = p->org[1] - right[1] - up[1];
2378 v3f[ 2] = p->org[2] - right[2] - up[2];
2379 v3f[ 3] = p->org[0] - right[0] + up[0];
2380 v3f[ 4] = p->org[1] - right[1] + up[1];
2381 v3f[ 5] = p->org[2] - right[2] + up[2];
2382 v3f[ 6] = p->org[0] + right[0] + up[0];
2383 v3f[ 7] = p->org[1] + right[1] + up[1];
2384 v3f[ 8] = p->org[2] + right[2] + up[2];
2385 v3f[ 9] = p->org[0] + right[0] - up[0];
2386 v3f[10] = p->org[1] + right[1] - up[1];
2387 v3f[11] = p->org[2] + right[2] - up[2];
2388 t2f[0] = tex->s1;t2f[1] = tex->t2;
2389 t2f[2] = tex->s1;t2f[3] = tex->t1;
2390 t2f[4] = tex->s2;t2f[5] = tex->t1;
2391 t2f[6] = tex->s2;t2f[7] = tex->t2;
2393 case PARTICLE_SPARK:
2394 len = VectorLength(p->vel);
2395 VectorNormalize2(p->vel, up);
2396 lenfactor = p->stretch * 0.04 * len;
2397 if(lenfactor < size * 0.5)
2398 lenfactor = size * 0.5;
2399 VectorMA(p->org, -lenfactor, up, v);
2400 VectorMA(p->org, lenfactor, up, up2);
2401 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2402 t2f[0] = tex->s1;t2f[1] = tex->t2;
2403 t2f[2] = tex->s1;t2f[3] = tex->t1;
2404 t2f[4] = tex->s2;t2f[5] = tex->t1;
2405 t2f[6] = tex->s2;t2f[7] = tex->t2;
2407 case PARTICLE_VBEAM:
2408 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2409 VectorSubtract(p->vel, p->org, up);
2410 VectorNormalize(up);
2411 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2412 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2413 t2f[0] = tex->s2;t2f[1] = v[0];
2414 t2f[2] = tex->s1;t2f[3] = v[0];
2415 t2f[4] = tex->s1;t2f[5] = v[1];
2416 t2f[6] = tex->s2;t2f[7] = v[1];
2418 case PARTICLE_HBEAM:
2419 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2420 VectorSubtract(p->vel, p->org, up);
2421 VectorNormalize(up);
2422 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2423 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2424 t2f[0] = v[0];t2f[1] = tex->t1;
2425 t2f[2] = v[0];t2f[3] = tex->t2;
2426 t2f[4] = v[1];t2f[5] = tex->t2;
2427 t2f[6] = v[1];t2f[7] = tex->t1;
2432 // now render batches of particles based on blendmode and texture
2433 blendmode = PBLEND_INVALID;
2435 GL_LockArrays(0, numsurfaces*4);
2438 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2440 p = cl.particles + surfacelist[surfacelistindex];
2442 if (blendmode != p->blendmode)
2444 blendmode = p->blendmode;
2448 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2450 case PBLEND_INVALID:
2452 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2455 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2459 if (texture != particletexture[p->texnum].texture)
2461 texture = particletexture[p->texnum].texture;
2462 R_Mesh_TexBind(0, R_GetTexture(texture));
2465 // iterate until we find a change in settings
2466 batchstart = surfacelistindex++;
2467 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2469 p = cl.particles + surfacelist[surfacelistindex];
2470 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2474 batchcount = surfacelistindex - batchstart;
2475 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2477 GL_LockArrays(0, 0);
2480 void R_DrawParticles (void)
2483 float minparticledist;
2485 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2491 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2492 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2494 // LordHavoc: early out conditions
2495 if ((!cl.num_particles) || (!r_drawparticles.integer))
2498 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2499 gravity = frametime * cl.movevars_gravity;
2500 dvel = 1+4*frametime;
2501 decalfade = frametime * 255 / cl_decals_fadetime.value;
2502 update = frametime > 0;
2503 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2504 drawdist2 = drawdist2*drawdist2;
2506 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2510 if (cl.free_particle > i)
2511 cl.free_particle = i;
2517 if (p->delayedspawn > cl.time)
2519 p->delayedspawn = 0;
2523 p->size += p->sizeincrease * frametime;
2524 p->alpha -= p->alphafade * frametime;
2526 if (p->alpha <= 0 || p->die <= cl.time)
2529 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2531 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2533 if (p->typeindex == pt_blood)
2534 p->size += frametime * 8;
2536 p->vel[2] -= p->gravity * gravity;
2537 f = 1.0f - min(p->liquidfriction * frametime, 1);
2538 VectorScale(p->vel, f, p->vel);
2542 p->vel[2] -= p->gravity * gravity;
2545 f = 1.0f - min(p->airfriction * frametime, 1);
2546 VectorScale(p->vel, f, p->vel);
2550 VectorCopy(p->org, oldorg);
2551 VectorMA(p->org, frametime, p->vel, p->org);
2552 if (p->bounce && cl.time >= p->delayedcollisions)
2554 trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
2555 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2556 // or if the trace hit something flagged as NOIMPACT
2557 // then remove the particle
2558 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2560 VectorCopy(trace.endpos, p->org);
2561 // react if the particle hit something
2562 if (trace.fraction < 1)
2564 VectorCopy(trace.endpos, p->org);
2566 if (p->staintexnum >= 0)
2568 // blood - splash on solid
2569 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2572 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2573 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2574 if (cl_decals.integer)
2576 // create a decal for the blood splat
2577 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, 0xFFFFFF ^ p->staincolor, 0xFFFFFF ^ p->staincolor, p->staintexnum, p->size * 2, p->alpha); // staincolor needs to be inverted for decals!
2582 if (p->typeindex == pt_blood)
2584 // blood - splash on solid
2585 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2587 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2589 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)));
2590 if (cl_decals.integer)
2592 // create a decal for the blood splat
2593 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 * 2, p->alpha);
2598 else if (p->bounce < 0)
2600 // bounce -1 means remove on impact
2605 // anything else - bounce off solid
2606 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2607 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2608 if (DotProduct(p->vel, p->vel) < 0.03)
2609 VectorClear(p->vel);
2615 if (p->typeindex != pt_static)
2617 switch (p->typeindex)
2619 case pt_entityparticle:
2620 // particle that removes itself after one rendered frame
2627 a = CL_PointSuperContents(p->org);
2628 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2632 a = CL_PointSuperContents(p->org);
2633 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2637 a = CL_PointSuperContents(p->org);
2638 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2642 if (cl.time > p->time2)
2645 p->time2 = cl.time + (rand() & 3) * 0.1;
2646 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2647 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2649 a = CL_PointSuperContents(p->org);
2650 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2658 else if (p->delayedspawn)
2661 // don't render particles too close to the view (they chew fillrate)
2662 // also don't render particles behind the view (useless)
2663 // further checks to cull to the frustum would be too slow here
2664 switch(p->typeindex)
2667 // beams have no culling
2668 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2671 if(cl_particles_visculling.integer)
2672 if (!r_refdef.viewcache.world_novis)
2673 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2675 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2677 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2680 // anything else just has to be in front of the viewer and visible at this distance
2681 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2682 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2689 if (cl.free_particle > i)
2690 cl.free_particle = i;
2693 // reduce cl.num_particles if possible
2694 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2697 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2699 particle_t *oldparticles = cl.particles;
2700 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2701 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2702 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2703 Mem_Free(oldparticles);