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 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
256 if (!strcmp(argv[0], "effect"))
261 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
263 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
266 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
268 if (particleeffectname[effectnameindex][0])
270 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
275 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
279 // if we run out of names, abort
280 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
282 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
285 info = particleeffectinfo + effectinfoindex;
286 info->effectnameindex = effectnameindex;
287 info->particletype = pt_alphastatic;
288 info->blendmode = particletype[info->particletype].blendmode;
289 info->orientation = particletype[info->particletype].orientation;
290 info->tex[0] = tex_particle;
291 info->tex[1] = tex_particle;
292 info->color[0] = 0xFFFFFF;
293 info->color[1] = 0xFFFFFF;
297 info->alpha[1] = 256;
298 info->alpha[2] = 256;
299 info->time[0] = 9999;
300 info->time[1] = 9999;
301 VectorSet(info->lightcolor, 1, 1, 1);
302 info->lightshadow = true;
303 info->lighttime = 9999;
304 info->stretchfactor = 1;
305 info->staincolor[0] = (unsigned int)-1;
306 info->staincolor[1] = (unsigned int)-1;
307 info->staintex[0] = -1;
308 info->staintex[1] = -1;
310 else if (info == NULL)
312 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
315 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
316 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
317 else if (!strcmp(argv[0], "type"))
320 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
321 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
322 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
323 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
324 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
325 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
326 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
327 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
328 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
329 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
330 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
331 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
332 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
333 info->blendmode = particletype[info->particletype].blendmode;
334 info->orientation = particletype[info->particletype].orientation;
336 else if (!strcmp(argv[0], "blend"))
339 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
340 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
341 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
342 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
344 else if (!strcmp(argv[0], "orientation"))
347 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
348 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
349 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
350 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
351 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
353 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
354 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
355 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
356 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
357 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
358 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
359 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
360 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
361 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
362 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
363 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
364 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
365 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
366 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
367 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
368 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
369 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
370 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
371 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
372 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
373 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
374 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
375 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
376 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
377 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
378 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
379 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
380 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1;}
382 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
391 int CL_ParticleEffectIndexForName(const char *name)
394 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
395 if (!strcmp(particleeffectname[i], name))
400 const char *CL_ParticleEffectNameForIndex(int i)
402 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
404 return particleeffectname[i];
407 // MUST match effectnameindex_t in client.h
408 static const char *standardeffectnames[EFFECT_TOTAL] =
432 "TE_TEI_BIGEXPLOSION",
448 void CL_Particles_LoadEffectInfo(void)
451 unsigned char *filedata;
452 fs_offset_t filesize;
453 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
454 memset(particleeffectname, 0, sizeof(particleeffectname));
455 for (i = 0;i < EFFECT_TOTAL;i++)
456 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
457 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
460 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
470 void CL_ReadPointFile_f (void);
471 void CL_Particles_Init (void)
473 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)");
474 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
476 Cvar_RegisterVariable (&cl_particles);
477 Cvar_RegisterVariable (&cl_particles_quality);
478 Cvar_RegisterVariable (&cl_particles_alpha);
479 Cvar_RegisterVariable (&cl_particles_size);
480 Cvar_RegisterVariable (&cl_particles_quake);
481 Cvar_RegisterVariable (&cl_particles_blood);
482 Cvar_RegisterVariable (&cl_particles_blood_alpha);
483 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
484 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
485 Cvar_RegisterVariable (&cl_particles_explosions_shell);
486 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
487 Cvar_RegisterVariable (&cl_particles_rain);
488 Cvar_RegisterVariable (&cl_particles_snow);
489 Cvar_RegisterVariable (&cl_particles_smoke);
490 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
491 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
492 Cvar_RegisterVariable (&cl_particles_sparks);
493 Cvar_RegisterVariable (&cl_particles_bubbles);
494 Cvar_RegisterVariable (&cl_particles_visculling);
495 Cvar_RegisterVariable (&cl_decals);
496 Cvar_RegisterVariable (&cl_decals_visculling);
497 Cvar_RegisterVariable (&cl_decals_time);
498 Cvar_RegisterVariable (&cl_decals_fadetime);
501 void CL_Particles_Shutdown (void)
505 // list of all 26 parameters:
506 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
507 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
508 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
509 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
510 // palpha - opacity of particle as 0-255 (can be more than 255)
511 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
512 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
513 // pgravity - how much effect gravity has on the particle (0-1)
514 // 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
515 // px,py,pz - starting origin of particle
516 // pvx,pvy,pvz - starting velocity of particle
517 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
518 // blendmode - one of the PBLEND_ values
519 // orientation - one of the PARTICLE_ values
520 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
521 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
522 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)
527 if (!cl_particles.integer)
529 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
530 if (cl.free_particle >= cl.max_particles)
533 lifetime = palpha / min(1, palphafade);
534 part = &cl.particles[cl.free_particle++];
535 if (cl.num_particles < cl.free_particle)
536 cl.num_particles = cl.free_particle;
537 memset(part, 0, sizeof(*part));
538 part->typeindex = ptypeindex;
539 part->blendmode = blendmode;
540 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
542 particletexture_t *tex = &particletexture[ptex];
543 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
544 part->orientation = PARTICLE_VBEAM;
546 part->orientation = PARTICLE_HBEAM;
549 part->orientation = orientation;
550 l2 = (int)lhrandom(0.5, 256.5);
552 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
553 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
554 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
555 part->staintexnum = staintex;
556 if(staincolor1 >= 0 && staincolor2 >= 0)
558 l2 = (int)lhrandom(0.5, 256.5);
560 if(blendmode == PBLEND_INVMOD)
562 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
563 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
564 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
568 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
569 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
570 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
572 if(r > 0xFF) r = 0xFF;
573 if(g > 0xFF) g = 0xFF;
574 if(b > 0xFF) b = 0xFF;
578 r = part->color[0]; // -1 is shorthand for stain = particle color
582 part->staincolor = r * 65536 + g * 256 + b;
585 part->sizeincrease = psizeincrease;
586 part->alpha = palpha;
587 part->alphafade = palphafade;
588 part->gravity = pgravity;
589 part->bounce = pbounce;
590 part->stretch = stretch;
592 part->org[0] = px + originjitter * v[0];
593 part->org[1] = py + originjitter * v[1];
594 part->org[2] = pz + originjitter * v[2];
595 part->vel[0] = pvx + velocityjitter * v[0];
596 part->vel[1] = pvy + velocityjitter * v[1];
597 part->vel[2] = pvz + velocityjitter * v[2];
599 part->airfriction = pairfriction;
600 part->liquidfriction = pliquidfriction;
601 part->die = cl.time + lifetime;
602 part->delayedcollisions = 0;
603 part->qualityreduction = pqualityreduction;
604 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
605 if (part->typeindex == pt_rain)
609 float lifetime = part->die - cl.time;
612 // turn raindrop into simple spark and create delayedspawn splash effect
613 part->typeindex = pt_spark;
615 VectorMA(part->org, lifetime, part->vel, endvec);
616 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
617 part->die = cl.time + lifetime * trace.fraction;
618 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);
621 part2->delayedspawn = part->die;
622 part2->die += part->die - cl.time;
623 for (i = rand() & 7;i < 10;i++)
625 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);
628 part2->delayedspawn = part->die;
629 part2->die += part->die - cl.time;
634 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
636 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
639 VectorMA(part->org, lifetime, part->vel, endvec);
640 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
641 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
646 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
650 if (!cl_decals.integer)
652 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
653 if (cl.free_decal >= cl.max_decals)
655 decal = &cl.decals[cl.free_decal++];
656 if (cl.num_decals < cl.free_decal)
657 cl.num_decals = cl.free_decal;
658 memset(decal, 0, sizeof(*decal));
659 decal->typeindex = pt_decal;
660 decal->texnum = texnum;
661 VectorAdd(org, normal, decal->org);
662 VectorCopy(normal, decal->normal);
664 decal->alpha = alpha;
665 decal->time2 = cl.time;
666 l2 = (int)lhrandom(0.5, 256.5);
668 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
669 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
670 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
671 decal->owner = hitent;
672 decal->clusterindex = -1000; // no vis culling unless we're sure
675 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
676 decal->ownermodel = cl.entities[decal->owner].render.model;
677 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
678 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
682 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
684 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
686 decal->clusterindex = leaf->clusterindex;
691 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
694 float bestfrac, bestorg[3], bestnormal[3];
696 int besthitent = 0, hitent;
699 for (i = 0;i < 32;i++)
702 VectorMA(org, maxdist, org2, org2);
703 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
704 // take the closest trace result that doesn't end up hitting a NOMARKS
705 // surface (sky for example)
706 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
708 bestfrac = trace.fraction;
710 VectorCopy(trace.endpos, bestorg);
711 VectorCopy(trace.plane.normal, bestnormal);
715 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
718 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
719 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
720 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)
723 matrix4x4_t tempmatrix;
724 VectorLerp(originmins, 0.5, originmaxs, center);
725 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
726 if (effectnameindex == EFFECT_SVC_PARTICLE)
728 if (cl_particles.integer)
730 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
732 CL_ParticleExplosion(center);
733 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
734 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
737 count *= cl_particles_quality.value;
738 for (;count > 0;count--)
740 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
741 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);
746 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
747 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
748 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
749 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
750 else if (effectnameindex == EFFECT_TE_SPIKE)
752 if (cl_particles_bulletimpacts.integer)
754 if (cl_particles_quake.integer)
756 if (cl_particles_smoke.integer)
757 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
761 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
762 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
763 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);
767 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
768 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
770 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
772 if (cl_particles_bulletimpacts.integer)
774 if (cl_particles_quake.integer)
776 if (cl_particles_smoke.integer)
777 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
781 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
782 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
783 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);
787 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
788 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
789 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);
791 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
793 if (cl_particles_bulletimpacts.integer)
795 if (cl_particles_quake.integer)
797 if (cl_particles_smoke.integer)
798 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
802 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
803 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
804 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);
808 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
809 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
811 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
813 if (cl_particles_bulletimpacts.integer)
815 if (cl_particles_quake.integer)
817 if (cl_particles_smoke.integer)
818 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
822 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
823 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
824 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);
828 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
829 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
830 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);
832 else if (effectnameindex == EFFECT_TE_BLOOD)
834 if (!cl_particles_blood.integer)
836 if (cl_particles_quake.integer)
837 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
840 static double bloodaccumulator = 0;
841 //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);
842 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
843 for (;bloodaccumulator > 0;bloodaccumulator--)
844 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);
847 else if (effectnameindex == EFFECT_TE_SPARK)
848 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
849 else if (effectnameindex == EFFECT_TE_PLASMABURN)
851 // plasma scorch mark
852 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
853 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
854 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
856 else if (effectnameindex == EFFECT_TE_GUNSHOT)
858 if (cl_particles_bulletimpacts.integer)
860 if (cl_particles_quake.integer)
861 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
864 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
865 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
866 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);
870 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
871 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
873 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
875 if (cl_particles_bulletimpacts.integer)
877 if (cl_particles_quake.integer)
878 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
881 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
882 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
883 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);
887 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
888 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
889 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
891 else if (effectnameindex == EFFECT_TE_EXPLOSION)
893 CL_ParticleExplosion(center);
894 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);
896 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
898 CL_ParticleExplosion(center);
899 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);
901 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
903 if (cl_particles_quake.integer)
906 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
909 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);
911 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);
915 CL_ParticleExplosion(center);
916 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);
918 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
919 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);
920 else if (effectnameindex == EFFECT_TE_FLAMEJET)
922 count *= cl_particles_quality.value;
924 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);
926 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
928 float i, j, inc, vel;
931 inc = 8 / cl_particles_quality.value;
932 for (i = -128;i < 128;i += inc)
934 for (j = -128;j < 128;j += inc)
936 dir[0] = j + lhrandom(0, inc);
937 dir[1] = i + lhrandom(0, inc);
939 org[0] = center[0] + dir[0];
940 org[1] = center[1] + dir[1];
941 org[2] = center[2] + lhrandom(0, 64);
942 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
943 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);
947 else if (effectnameindex == EFFECT_TE_TELEPORT)
949 float i, j, k, inc, vel;
952 if (cl_particles_quake.integer)
953 inc = 4 / cl_particles_quality.value;
955 inc = 8 / cl_particles_quality.value;
956 for (i = -16;i < 16;i += inc)
958 for (j = -16;j < 16;j += inc)
960 for (k = -24;k < 32;k += inc)
962 VectorSet(dir, i*8, j*8, k*8);
963 VectorNormalize(dir);
964 vel = lhrandom(50, 113);
965 if (cl_particles_quake.integer)
966 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);
968 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);
972 if (!cl_particles_quake.integer)
973 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);
974 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);
976 else if (effectnameindex == EFFECT_TE_TEI_G3)
977 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);
978 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
980 if (cl_particles_smoke.integer)
982 count *= 0.25f * cl_particles_quality.value;
984 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);
987 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
989 CL_ParticleExplosion(center);
990 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);
992 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
995 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
996 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
997 if (cl_particles_smoke.integer)
998 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
999 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);
1000 if (cl_particles_sparks.integer)
1001 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1002 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);
1003 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);
1005 else if (effectnameindex == EFFECT_EF_FLAME)
1007 count *= 300 * cl_particles_quality.value;
1009 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);
1010 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);
1012 else if (effectnameindex == EFFECT_EF_STARDUST)
1014 count *= 200 * cl_particles_quality.value;
1016 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);
1017 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);
1019 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1023 int smoke, blood, bubbles, r, color;
1025 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1028 Vector4Set(light, 0, 0, 0, 0);
1030 if (effectnameindex == EFFECT_TR_ROCKET)
1031 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1032 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1034 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1035 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1037 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1039 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1040 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1044 matrix4x4_t tempmatrix;
1045 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1046 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);
1047 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1051 if (!spawnparticles)
1054 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1057 VectorSubtract(originmaxs, originmins, dir);
1058 len = VectorNormalizeLength(dir);
1061 dec = -ent->persistent.trail_time;
1062 ent->persistent.trail_time += len;
1063 if (ent->persistent.trail_time < 0.01f)
1066 // if we skip out, leave it reset
1067 ent->persistent.trail_time = 0.0f;
1072 // advance into this frame to reach the first puff location
1073 VectorMA(originmins, dec, dir, pos);
1076 smoke = cl_particles.integer && cl_particles_smoke.integer;
1077 blood = cl_particles.integer && cl_particles_blood.integer;
1078 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1079 qd = 1.0f / cl_particles_quality.value;
1086 if (effectnameindex == EFFECT_TR_BLOOD)
1088 if (cl_particles_quake.integer)
1090 color = particlepalette[67 + (rand()&3)];
1091 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);
1096 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);
1099 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1101 if (cl_particles_quake.integer)
1104 color = particlepalette[67 + (rand()&3)];
1105 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);
1110 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);
1116 if (effectnameindex == EFFECT_TR_ROCKET)
1118 if (cl_particles_quake.integer)
1121 color = particlepalette[ramp3[r]];
1122 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);
1126 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);
1127 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);
1130 else if (effectnameindex == EFFECT_TR_GRENADE)
1132 if (cl_particles_quake.integer)
1135 color = particlepalette[ramp3[r]];
1136 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);
1140 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);
1143 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1145 if (cl_particles_quake.integer)
1148 color = particlepalette[52 + (rand()&7)];
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);
1150 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);
1152 else if (gamemode == GAME_GOODVSBAD2)
1155 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);
1159 color = particlepalette[20 + (rand()&7)];
1160 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);
1163 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1165 if (cl_particles_quake.integer)
1168 color = particlepalette[230 + (rand()&7)];
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);
1170 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);
1174 color = particlepalette[226 + (rand()&7)];
1175 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);
1178 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1180 if (cl_particles_quake.integer)
1182 color = particlepalette[152 + (rand()&3)];
1183 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);
1185 else if (gamemode == GAME_GOODVSBAD2)
1188 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);
1190 else if (gamemode == GAME_PRYDON)
1193 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);
1196 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);
1198 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1201 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);
1203 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1206 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);
1208 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1209 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);
1213 if (effectnameindex == EFFECT_TR_ROCKET)
1214 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);
1215 else if (effectnameindex == EFFECT_TR_GRENADE)
1216 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);
1218 // advance to next time and position
1221 VectorMA (pos, dec, dir, pos);
1224 ent->persistent.trail_time = len;
1226 else if (developer.integer >= 1)
1227 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1230 // this is also called on point effects with spawndlight = true and
1231 // spawnparticles = true
1232 // it is called CL_ParticleTrail because most code does not want to supply
1233 // these parameters, only trail handling does
1234 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)
1237 qboolean found = false;
1238 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1240 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1241 return; // no such effect
1243 VectorLerp(originmins, 0.5, originmaxs, center);
1244 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1246 int effectinfoindex;
1249 particleeffectinfo_t *info;
1251 vec3_t centervelocity;
1257 qboolean underwater;
1258 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1259 VectorLerp(originmins, 0.5, originmaxs, center);
1260 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1261 supercontents = CL_PointSuperContents(center);
1262 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1263 VectorSubtract(originmaxs, originmins, traildir);
1264 traillen = VectorLength(traildir);
1265 VectorNormalize(traildir);
1266 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1268 if (info->effectnameindex == effectnameindex)
1271 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1273 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1276 // spawn a dlight if requested
1277 if (info->lightradiusstart > 0 && spawndlight)
1279 matrix4x4_t tempmatrix;
1280 if (info->trailspacing > 0)
1281 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1283 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1284 if (info->lighttime > 0 && info->lightradiusfade > 0)
1286 // light flash (explosion, etc)
1287 // called when effect starts
1288 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);
1293 // called by CL_LinkNetworkEntity
1294 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1295 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);
1296 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1300 if (!spawnparticles)
1305 if (info->tex[1] > info->tex[0])
1307 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1308 tex = min(tex, info->tex[1] - 1);
1310 if(info->staintex[0] < 0)
1311 staintex = info->staintex[0];
1314 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1315 staintex = min(staintex, info->staintex[1] - 1);
1317 if (info->particletype == pt_decal)
1318 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]);
1319 else if (info->orientation == PARTICLE_HBEAM)
1320 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);
1323 if (!cl_particles.integer)
1325 switch (info->particletype)
1327 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1328 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1329 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1330 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1331 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1332 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1335 VectorCopy(originmins, trailpos);
1336 if (info->trailspacing > 0)
1338 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1339 trailstep = info->trailspacing / cl_particles_quality.value;
1343 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1346 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1347 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1349 if (info->tex[1] > info->tex[0])
1351 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1352 tex = min(tex, info->tex[1] - 1);
1356 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1357 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1358 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1361 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);
1363 VectorMA(trailpos, trailstep, traildir, trailpos);
1370 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1373 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)
1375 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1383 void CL_EntityParticles (const entity_t *ent)
1386 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1387 static vec3_t avelocities[NUMVERTEXNORMALS];
1388 if (!cl_particles.integer) return;
1389 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1391 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1393 if (!avelocities[0][0])
1394 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1395 avelocities[0][i] = lhrandom(0, 2.55);
1397 for (i = 0;i < NUMVERTEXNORMALS;i++)
1399 yaw = cl.time * avelocities[i][0];
1400 pitch = cl.time * avelocities[i][1];
1401 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1402 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1403 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1404 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);
1409 void CL_ReadPointFile_f (void)
1411 vec3_t org, leakorg;
1413 char *pointfile = NULL, *pointfilepos, *t, tchar;
1414 char name[MAX_OSPATH];
1419 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1420 strlcat (name, ".pts", sizeof (name));
1421 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1424 Con_Printf("Could not open %s\n", name);
1428 Con_Printf("Reading %s...\n", name);
1429 VectorClear(leakorg);
1432 pointfilepos = pointfile;
1433 while (*pointfilepos)
1435 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1440 while (*t && *t != '\n' && *t != '\r')
1444 #if _MSC_VER >= 1400
1445 #define sscanf sscanf_s
1447 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1453 VectorCopy(org, leakorg);
1456 if (cl.num_particles < cl.max_particles - 3)
1459 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);
1462 Mem_Free(pointfile);
1463 VectorCopy(leakorg, org);
1464 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1466 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);
1467 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);
1468 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);
1473 CL_ParseParticleEffect
1475 Parse an effect out of the server message
1478 void CL_ParseParticleEffect (void)
1481 int i, count, msgcount, color;
1483 MSG_ReadVector(org, cls.protocol);
1484 for (i=0 ; i<3 ; i++)
1485 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1486 msgcount = MSG_ReadByte ();
1487 color = MSG_ReadByte ();
1489 if (msgcount == 255)
1494 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1499 CL_ParticleExplosion
1503 void CL_ParticleExplosion (const vec3_t org)
1509 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1510 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1512 if (cl_particles_quake.integer)
1514 for (i = 0;i < 1024;i++)
1520 color = particlepalette[ramp1[r]];
1521 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);
1525 color = particlepalette[ramp2[r]];
1526 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);
1532 i = CL_PointSuperContents(org);
1533 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1535 if (cl_particles.integer && cl_particles_bubbles.integer)
1536 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1537 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);
1541 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1543 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1547 for (k = 0;k < 16;k++)
1550 VectorMA(org, 128, v2, v);
1551 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1552 if (trace.fraction >= 0.1)
1555 VectorSubtract(trace.endpos, org, v2);
1556 VectorScale(v2, 2.0f, v2);
1557 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);
1563 if (cl_particles_explosions_shell.integer)
1564 R_NewExplosion(org);
1569 CL_ParticleExplosion2
1573 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1576 if (!cl_particles.integer) return;
1578 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1580 k = particlepalette[colorStart + (i % colorLength)];
1581 if (cl_particles_quake.integer)
1582 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);
1584 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);
1588 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1590 if (cl_particles_sparks.integer)
1592 sparkcount *= cl_particles_quality.value;
1593 while(sparkcount-- > 0)
1594 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);
1598 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1600 if (cl_particles_smoke.integer)
1602 smokecount *= cl_particles_quality.value;
1603 while(smokecount-- > 0)
1604 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);
1608 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)
1611 if (!cl_particles.integer) return;
1613 count = (int)(count * cl_particles_quality.value);
1616 k = particlepalette[colorbase + (rand()&3)];
1617 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);
1621 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1624 float minz, maxz, lifetime = 30;
1625 if (!cl_particles.integer) return;
1626 if (dir[2] < 0) // falling
1628 minz = maxs[2] + dir[2] * 0.1;
1631 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1636 maxz = maxs[2] + dir[2] * 0.1;
1638 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1641 count = (int)(count * cl_particles_quality.value);
1646 if (!cl_particles_rain.integer) break;
1647 count *= 4; // ick, this should be in the mod or maps?
1651 k = particlepalette[colorbase + (rand()&3)];
1652 if (gamemode == GAME_GOODVSBAD2)
1653 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);
1655 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);
1659 if (!cl_particles_snow.integer) break;
1662 k = particlepalette[colorbase + (rand()&3)];
1663 if (gamemode == GAME_GOODVSBAD2)
1664 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);
1666 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);
1670 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1674 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1675 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1676 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1677 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1679 #define PARTICLETEXTURESIZE 64
1680 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1682 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1686 dz = 1 - (dx*dx+dy*dy);
1687 if (dz > 0) // it does hit the sphere
1691 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1692 VectorNormalize(normal);
1693 dot = DotProduct(normal, light);
1694 if (dot > 0.5) // interior reflection
1695 f += ((dot * 2) - 1);
1696 else if (dot < -0.5) // exterior reflection
1697 f += ((dot * -2) - 1);
1699 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1700 VectorNormalize(normal);
1701 dot = DotProduct(normal, light);
1702 if (dot > 0.5) // interior reflection
1703 f += ((dot * 2) - 1);
1704 else if (dot < -0.5) // exterior reflection
1705 f += ((dot * -2) - 1);
1707 f += 16; // just to give it a haze so you can see the outline
1708 f = bound(0, f, 255);
1709 return (unsigned char) f;
1715 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1716 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1718 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1719 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1720 *width = particlefontcellwidth;
1721 *height = particlefontcellheight;
1724 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1726 int basex, basey, w, h, y;
1727 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1728 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1729 Sys_Error("invalid particle texture size for autogenerating");
1730 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1731 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1734 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1737 float cx, cy, dx, dy, f, iradius;
1739 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1740 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1741 iradius = 1.0f / radius;
1742 alpha *= (1.0f / 255.0f);
1743 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1745 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1749 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1754 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1755 d[0] += (int)(f * (blue - d[0]));
1756 d[1] += (int)(f * (green - d[1]));
1757 d[2] += (int)(f * (red - d[2]));
1763 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1766 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1768 data[0] = bound(minb, data[0], maxb);
1769 data[1] = bound(ming, data[1], maxg);
1770 data[2] = bound(minr, data[2], maxr);
1774 void particletextureinvert(unsigned char *data)
1777 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1779 data[0] = 255 - data[0];
1780 data[1] = 255 - data[1];
1781 data[2] = 255 - data[2];
1785 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1786 static void R_InitBloodTextures (unsigned char *particletexturedata)
1789 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1792 for (i = 0;i < 8;i++)
1794 memset(&data[0][0][0], 255, sizeof(data));
1795 for (k = 0;k < 24;k++)
1796 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1797 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1798 particletextureinvert(&data[0][0][0]);
1799 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1803 for (i = 0;i < 8;i++)
1805 memset(&data[0][0][0], 255, sizeof(data));
1807 for (j = 1;j < 10;j++)
1808 for (k = min(j, m - 1);k < m;k++)
1809 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1810 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1811 particletextureinvert(&data[0][0][0]);
1812 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1817 //uncomment this to make engine save out particle font to a tga file when run
1818 //#define DUMPPARTICLEFONT
1820 static void R_InitParticleTexture (void)
1822 int x, y, d, i, k, m;
1823 int basex, basey, w, h;
1827 fs_offset_t filesize;
1829 // a note: decals need to modulate (multiply) the background color to
1830 // properly darken it (stain), and they need to be able to alpha fade,
1831 // this is a very difficult challenge because it means fading to white
1832 // (no change to background) rather than black (darkening everything
1833 // behind the whole decal polygon), and to accomplish this the texture is
1834 // inverted (dark red blood on white background becomes brilliant cyan
1835 // and white on black background) so we can alpha fade it to black, then
1836 // we invert it again during the blendfunc to make it work...
1838 #ifndef DUMPPARTICLEFONT
1839 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1840 if (particlefonttexture)
1842 // TODO maybe allow custom grid size?
1843 particlefontwidth = image_width;
1844 particlefontheight = image_height;
1845 particlefontcellwidth = image_width / 8;
1846 particlefontcellheight = image_height / 8;
1847 particlefontcols = 8;
1848 particlefontrows = 8;
1853 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1854 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1856 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1857 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1858 particlefontcols = 8;
1859 particlefontrows = 8;
1861 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1864 for (i = 0;i < 8;i++)
1866 memset(&data[0][0][0], 255, sizeof(data));
1869 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1871 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1872 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1874 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1876 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1877 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1879 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1880 d = (noise2[y][x] - 128) * 3 + 192;
1882 d = (int)(d * (1-(dx*dx+dy*dy)));
1883 d = (d * noise1[y][x]) >> 7;
1884 d = bound(0, d, 255);
1885 data[y][x][3] = (unsigned char) d;
1892 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1896 memset(&data[0][0][0], 255, sizeof(data));
1897 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1899 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1900 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1902 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1903 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1904 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1907 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1910 memset(&data[0][0][0], 255, sizeof(data));
1911 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1913 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1914 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1916 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1917 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1918 d = bound(0, d, 255);
1919 data[y][x][3] = (unsigned char) d;
1922 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1925 memset(&data[0][0][0], 255, sizeof(data));
1926 light[0] = 1;light[1] = 1;light[2] = 1;
1927 VectorNormalize(light);
1928 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1930 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1931 // stretch upper half of bubble by +50% and shrink lower half by -50%
1932 // (this gives an elongated teardrop shape)
1934 dy = (dy - 0.5f) * 2.0f;
1936 dy = (dy - 0.5f) / 1.5f;
1937 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1939 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1940 // shrink bubble width to half
1942 data[y][x][3] = shadebubble(dx, dy, light);
1945 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1948 memset(&data[0][0][0], 255, sizeof(data));
1949 light[0] = 1;light[1] = 1;light[2] = 1;
1950 VectorNormalize(light);
1951 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1953 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1954 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1956 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1957 data[y][x][3] = shadebubble(dx, dy, light);
1960 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1962 // Blood particles and blood decals
1963 R_InitBloodTextures (particletexturedata);
1966 for (i = 0;i < 8;i++)
1968 memset(&data[0][0][0], 255, sizeof(data));
1969 for (k = 0;k < 12;k++)
1970 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1971 for (k = 0;k < 3;k++)
1972 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1973 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1974 particletextureinvert(&data[0][0][0]);
1975 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1978 #ifdef DUMPPARTICLEFONT
1979 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1982 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1984 Mem_Free(particletexturedata);
1986 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1988 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
1989 particletexture[i].texture = particlefonttexture;
1990 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
1991 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
1992 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
1993 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
1996 #ifndef DUMPPARTICLEFONT
1997 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1998 if (!particletexture[tex_beam].texture)
2001 unsigned char noise3[64][64], data2[64][16][4];
2003 fractalnoise(&noise3[0][0], 64, 4);
2005 for (y = 0;y < 64;y++)
2007 dy = (y - 0.5f*64) / (64*0.5f-1);
2008 for (x = 0;x < 16;x++)
2010 dx = (x - 0.5f*16) / (16*0.5f-2);
2011 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2012 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2013 data2[y][x][3] = 255;
2017 #ifdef DUMPPARTICLEFONT
2018 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2020 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
2022 particletexture[tex_beam].s1 = 0;
2023 particletexture[tex_beam].t1 = 0;
2024 particletexture[tex_beam].s2 = 1;
2025 particletexture[tex_beam].t2 = 1;
2027 // now load an texcoord/texture override file
2028 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2035 if(!COM_ParseToken_Simple(&bufptr, true, false))
2037 if(!strcmp(com_token, "\n"))
2038 continue; // empty line
2039 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2040 particletexture[i].texture = particlefonttexture;
2042 if (!COM_ParseToken_Simple(&bufptr, true, false))
2044 if (!strcmp(com_token, "\n"))
2046 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2049 particletexture[i].s1 = atof(com_token);
2051 if (!COM_ParseToken_Simple(&bufptr, true, false))
2053 if (!strcmp(com_token, "\n"))
2055 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2058 particletexture[i].t1 = atof(com_token);
2060 if (!COM_ParseToken_Simple(&bufptr, true, false))
2062 if (!strcmp(com_token, "\n"))
2064 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2067 particletexture[i].s2 = atof(com_token);
2069 if (!COM_ParseToken_Simple(&bufptr, true, false))
2071 if (!strcmp(com_token, "\n"))
2073 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2076 particletexture[i].t2 = atof(com_token);
2082 static void r_part_start(void)
2085 // generate particlepalette for convenience from the main one
2086 for (i = 0;i < 256;i++)
2087 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2088 particletexturepool = R_AllocTexturePool();
2089 R_InitParticleTexture ();
2090 CL_Particles_LoadEffectInfo();
2093 static void r_part_shutdown(void)
2095 R_FreeTexturePool(&particletexturepool);
2098 static void r_part_newmap(void)
2100 CL_Particles_LoadEffectInfo();
2103 #define BATCHSIZE 256
2104 unsigned short particle_elements[BATCHSIZE*6];
2106 void R_Particles_Init (void)
2109 for (i = 0;i < BATCHSIZE;i++)
2111 particle_elements[i*6+0] = i*4+0;
2112 particle_elements[i*6+1] = i*4+1;
2113 particle_elements[i*6+2] = i*4+2;
2114 particle_elements[i*6+3] = i*4+0;
2115 particle_elements[i*6+4] = i*4+2;
2116 particle_elements[i*6+5] = i*4+3;
2119 Cvar_RegisterVariable(&r_drawparticles);
2120 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2121 Cvar_RegisterVariable(&r_drawdecals);
2122 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2123 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2126 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2128 int surfacelistindex;
2130 float *v3f, *t2f, *c4f;
2131 particletexture_t *tex;
2132 float right[3], up[3], size, ca;
2133 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
2134 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2136 r_refdef.stats.decals += numsurfaces;
2137 R_Mesh_Matrix(&identitymatrix);
2138 R_Mesh_ResetTextureState();
2139 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2140 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2141 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2142 R_SetupGenericShader(true);
2143 GL_DepthMask(false);
2144 GL_DepthRange(0, 1);
2145 GL_PolygonOffset(0, 0);
2147 GL_CullFace(GL_NONE);
2149 // generate all the vertices at once
2150 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2152 d = cl.decals + surfacelist[surfacelistindex];
2155 c4f = particle_color4f + 16*surfacelistindex;
2156 ca = d->alpha * alphascale;
2157 if (r_refdef.fogenabled)
2158 ca *= FogPoint_World(d->org);
2159 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2160 Vector4Copy(c4f, c4f + 4);
2161 Vector4Copy(c4f, c4f + 8);
2162 Vector4Copy(c4f, c4f + 12);
2164 // calculate vertex positions
2165 size = d->size * cl_particles_size.value;
2166 VectorVectors(d->normal, right, up);
2167 VectorScale(right, size, right);
2168 VectorScale(up, size, up);
2169 v3f = particle_vertex3f + 12*surfacelistindex;
2170 v3f[ 0] = d->org[0] - right[0] - up[0];
2171 v3f[ 1] = d->org[1] - right[1] - up[1];
2172 v3f[ 2] = d->org[2] - right[2] - up[2];
2173 v3f[ 3] = d->org[0] - right[0] + up[0];
2174 v3f[ 4] = d->org[1] - right[1] + up[1];
2175 v3f[ 5] = d->org[2] - right[2] + up[2];
2176 v3f[ 6] = d->org[0] + right[0] + up[0];
2177 v3f[ 7] = d->org[1] + right[1] + up[1];
2178 v3f[ 8] = d->org[2] + right[2] + up[2];
2179 v3f[ 9] = d->org[0] + right[0] - up[0];
2180 v3f[10] = d->org[1] + right[1] - up[1];
2181 v3f[11] = d->org[2] + right[2] - up[2];
2183 // calculate texcoords
2184 tex = &particletexture[d->texnum];
2185 t2f = particle_texcoord2f + 8*surfacelistindex;
2186 t2f[0] = tex->s1;t2f[1] = tex->t2;
2187 t2f[2] = tex->s1;t2f[3] = tex->t1;
2188 t2f[4] = tex->s2;t2f[5] = tex->t1;
2189 t2f[6] = tex->s2;t2f[7] = tex->t2;
2192 // now render the decals all at once
2193 // (this assumes they all use one particle font texture!)
2194 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2195 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2196 GL_LockArrays(0, numsurfaces*4);
2197 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2198 GL_LockArrays(0, 0);
2201 void R_DrawDecals (void)
2204 int drawdecals = r_drawdecals.integer;
2210 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2211 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2213 // LordHavoc: early out conditions
2217 decalfade = frametime * 256 / cl_decals_fadetime.value;
2218 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2219 drawdist2 = drawdist2*drawdist2;
2221 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2223 if (!decal->typeindex)
2226 if (cl.time > decal->time2 + cl_decals_time.value)
2228 decal->alpha -= decalfade;
2229 if (decal->alpha <= 0)
2235 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2237 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2238 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2244 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2250 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))
2251 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2254 decal->typeindex = 0;
2255 if (cl.free_decal > i)
2259 // reduce cl.num_decals if possible
2260 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2263 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2265 decal_t *olddecals = cl.decals;
2266 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2267 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2268 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2269 Mem_Free(olddecals);
2273 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2275 int surfacelistindex;
2276 int batchstart, batchcount;
2277 const particle_t *p;
2279 rtexture_t *texture;
2280 float *v3f, *t2f, *c4f;
2281 particletexture_t *tex;
2282 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2283 float ambient[3], diffuse[3], diffusenormal[3];
2284 vec4_t colormultiplier;
2285 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2287 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));
2289 r_refdef.stats.particles += numsurfaces;
2290 R_Mesh_Matrix(&identitymatrix);
2291 R_Mesh_ResetTextureState();
2292 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2293 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2294 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2295 R_SetupGenericShader(true);
2296 GL_DepthMask(false);
2297 GL_DepthRange(0, 1);
2298 GL_PolygonOffset(0, 0);
2300 GL_CullFace(GL_NONE);
2302 // first generate all the vertices at once
2303 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2305 p = cl.particles + surfacelist[surfacelistindex];
2307 blendmode = p->blendmode;
2309 c4f[0] = p->color[0] * colormultiplier[0];
2310 c4f[1] = p->color[1] * colormultiplier[1];
2311 c4f[2] = p->color[2] * colormultiplier[2];
2312 c4f[3] = p->alpha * colormultiplier[3];
2315 case PBLEND_INVALID:
2318 // additive and modulate can just fade out in fog (this is correct)
2319 if (r_refdef.fogenabled)
2320 c4f[3] *= FogPoint_World(p->org);
2321 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2328 // note: lighting is not cheap!
2329 if (particletype[p->typeindex].lighting)
2331 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2332 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2333 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2334 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2336 // mix in the fog color
2337 if (r_refdef.fogenabled)
2339 fog = FogPoint_World(p->org);
2341 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2342 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2343 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2347 // copy the color into the other three vertices
2348 Vector4Copy(c4f, c4f + 4);
2349 Vector4Copy(c4f, c4f + 8);
2350 Vector4Copy(c4f, c4f + 12);
2352 size = p->size * cl_particles_size.value;
2353 tex = &particletexture[p->texnum];
2354 switch(p->orientation)
2356 case PARTICLE_INVALID:
2357 case PARTICLE_BILLBOARD:
2358 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2359 VectorScale(r_refdef.view.up, size, up);
2360 v3f[ 0] = p->org[0] - right[0] - up[0];
2361 v3f[ 1] = p->org[1] - right[1] - up[1];
2362 v3f[ 2] = p->org[2] - right[2] - up[2];
2363 v3f[ 3] = p->org[0] - right[0] + up[0];
2364 v3f[ 4] = p->org[1] - right[1] + up[1];
2365 v3f[ 5] = p->org[2] - right[2] + up[2];
2366 v3f[ 6] = p->org[0] + right[0] + up[0];
2367 v3f[ 7] = p->org[1] + right[1] + up[1];
2368 v3f[ 8] = p->org[2] + right[2] + up[2];
2369 v3f[ 9] = p->org[0] + right[0] - up[0];
2370 v3f[10] = p->org[1] + right[1] - up[1];
2371 v3f[11] = p->org[2] + right[2] - up[2];
2372 t2f[0] = tex->s1;t2f[1] = tex->t2;
2373 t2f[2] = tex->s1;t2f[3] = tex->t1;
2374 t2f[4] = tex->s2;t2f[5] = tex->t1;
2375 t2f[6] = tex->s2;t2f[7] = tex->t2;
2377 case PARTICLE_ORIENTED_DOUBLESIDED:
2378 VectorVectors(p->vel, right, up);
2379 VectorScale(right, size * p->stretch, right);
2380 VectorScale(up, size, up);
2381 v3f[ 0] = p->org[0] - right[0] - up[0];
2382 v3f[ 1] = p->org[1] - right[1] - up[1];
2383 v3f[ 2] = p->org[2] - right[2] - up[2];
2384 v3f[ 3] = p->org[0] - right[0] + up[0];
2385 v3f[ 4] = p->org[1] - right[1] + up[1];
2386 v3f[ 5] = p->org[2] - right[2] + up[2];
2387 v3f[ 6] = p->org[0] + right[0] + up[0];
2388 v3f[ 7] = p->org[1] + right[1] + up[1];
2389 v3f[ 8] = p->org[2] + right[2] + up[2];
2390 v3f[ 9] = p->org[0] + right[0] - up[0];
2391 v3f[10] = p->org[1] + right[1] - up[1];
2392 v3f[11] = p->org[2] + right[2] - up[2];
2393 t2f[0] = tex->s1;t2f[1] = tex->t2;
2394 t2f[2] = tex->s1;t2f[3] = tex->t1;
2395 t2f[4] = tex->s2;t2f[5] = tex->t1;
2396 t2f[6] = tex->s2;t2f[7] = tex->t2;
2398 case PARTICLE_SPARK:
2399 len = VectorLength(p->vel);
2400 VectorNormalize2(p->vel, up);
2401 lenfactor = p->stretch * 0.04 * len;
2402 if(lenfactor < size * 0.5)
2403 lenfactor = size * 0.5;
2404 VectorMA(p->org, -lenfactor, up, v);
2405 VectorMA(p->org, lenfactor, up, up2);
2406 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2407 t2f[0] = tex->s1;t2f[1] = tex->t2;
2408 t2f[2] = tex->s1;t2f[3] = tex->t1;
2409 t2f[4] = tex->s2;t2f[5] = tex->t1;
2410 t2f[6] = tex->s2;t2f[7] = tex->t2;
2412 case PARTICLE_VBEAM:
2413 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2414 VectorSubtract(p->vel, p->org, up);
2415 VectorNormalize(up);
2416 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2417 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2418 t2f[0] = tex->s2;t2f[1] = v[0];
2419 t2f[2] = tex->s1;t2f[3] = v[0];
2420 t2f[4] = tex->s1;t2f[5] = v[1];
2421 t2f[6] = tex->s2;t2f[7] = v[1];
2423 case PARTICLE_HBEAM:
2424 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2425 VectorSubtract(p->vel, p->org, up);
2426 VectorNormalize(up);
2427 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2428 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2429 t2f[0] = v[0];t2f[1] = tex->t1;
2430 t2f[2] = v[0];t2f[3] = tex->t2;
2431 t2f[4] = v[1];t2f[5] = tex->t2;
2432 t2f[6] = v[1];t2f[7] = tex->t1;
2437 // now render batches of particles based on blendmode and texture
2438 blendmode = PBLEND_INVALID;
2440 GL_LockArrays(0, numsurfaces*4);
2443 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2445 p = cl.particles + surfacelist[surfacelistindex];
2447 if (blendmode != p->blendmode)
2449 blendmode = p->blendmode;
2453 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2455 case PBLEND_INVALID:
2457 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2460 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2464 if (texture != particletexture[p->texnum].texture)
2466 texture = particletexture[p->texnum].texture;
2467 R_Mesh_TexBind(0, R_GetTexture(texture));
2470 // iterate until we find a change in settings
2471 batchstart = surfacelistindex++;
2472 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2474 p = cl.particles + surfacelist[surfacelistindex];
2475 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2479 batchcount = surfacelistindex - batchstart;
2480 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2482 GL_LockArrays(0, 0);
2485 void R_DrawParticles (void)
2488 int drawparticles = r_drawparticles.integer;
2489 float minparticledist;
2491 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2497 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2498 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2500 // LordHavoc: early out conditions
2501 if (!cl.num_particles)
2504 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2505 gravity = frametime * cl.movevars_gravity;
2506 dvel = 1+4*frametime;
2507 decalfade = frametime * 255 / cl_decals_fadetime.value;
2508 update = frametime > 0;
2509 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2510 drawdist2 = drawdist2*drawdist2;
2512 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2516 if (cl.free_particle > i)
2517 cl.free_particle = i;
2523 if (p->delayedspawn > cl.time)
2525 p->delayedspawn = 0;
2529 p->size += p->sizeincrease * frametime;
2530 p->alpha -= p->alphafade * frametime;
2532 if (p->alpha <= 0 || p->die <= cl.time)
2535 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2537 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2539 if (p->typeindex == pt_blood)
2540 p->size += frametime * 8;
2542 p->vel[2] -= p->gravity * gravity;
2543 f = 1.0f - min(p->liquidfriction * frametime, 1);
2544 VectorScale(p->vel, f, p->vel);
2548 p->vel[2] -= p->gravity * gravity;
2551 f = 1.0f - min(p->airfriction * frametime, 1);
2552 VectorScale(p->vel, f, p->vel);
2556 VectorCopy(p->org, oldorg);
2557 VectorMA(p->org, frametime, p->vel, p->org);
2558 if (p->bounce && cl.time >= p->delayedcollisions)
2560 trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
2561 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2562 // or if the trace hit something flagged as NOIMPACT
2563 // then remove the particle
2564 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2566 VectorCopy(trace.endpos, p->org);
2567 // react if the particle hit something
2568 if (trace.fraction < 1)
2570 VectorCopy(trace.endpos, p->org);
2572 if (p->staintexnum >= 0)
2574 // blood - splash on solid
2575 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2578 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2579 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2580 if (cl_decals.integer)
2582 // create a decal for the blood splat
2583 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!
2588 if (p->typeindex == pt_blood)
2590 // blood - splash on solid
2591 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2593 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2595 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)));
2596 if (cl_decals.integer)
2598 // create a decal for the blood splat
2599 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);
2604 else if (p->bounce < 0)
2606 // bounce -1 means remove on impact
2611 // anything else - bounce off solid
2612 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2613 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2614 if (DotProduct(p->vel, p->vel) < 0.03)
2615 VectorClear(p->vel);
2621 if (p->typeindex != pt_static)
2623 switch (p->typeindex)
2625 case pt_entityparticle:
2626 // particle that removes itself after one rendered frame
2633 a = CL_PointSuperContents(p->org);
2634 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2638 a = CL_PointSuperContents(p->org);
2639 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2643 a = CL_PointSuperContents(p->org);
2644 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2648 if (cl.time > p->time2)
2651 p->time2 = cl.time + (rand() & 3) * 0.1;
2652 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2653 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2655 a = CL_PointSuperContents(p->org);
2656 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2664 else if (p->delayedspawn)
2668 // don't render particles too close to the view (they chew fillrate)
2669 // also don't render particles behind the view (useless)
2670 // further checks to cull to the frustum would be too slow here
2671 switch(p->typeindex)
2674 // beams have no culling
2675 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2678 if(cl_particles_visculling.integer)
2679 if (!r_refdef.viewcache.world_novis)
2680 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2682 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2684 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2687 // anything else just has to be in front of the viewer and visible at this distance
2688 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2689 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2696 if (cl.free_particle > i)
2697 cl.free_particle = i;
2700 // reduce cl.num_particles if possible
2701 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2704 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2706 particle_t *oldparticles = cl.particles;
2707 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2708 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2709 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2710 Mem_Free(oldparticles);