- collect most sound infos in one table and use it to generate the menus - no more sound category checks with ranges - now only PAINx and FOOTSTEP* sounds use hardcoded index numbers enumsounds <list of categories> Lists sound index numbers and short descriptions for all sounds of one or more categories. The argument should contain one or more of PAIN, OWNPAIN, WEAPON, PICKUP, MOVEMENT, BULLET, OTHER, VOICECOM, TEAM, PUBLIC, FFA, FLAGONLY. All sounds with at least one of the flags will be listed. Flag names prefixed with "!" will exclude all matching sound from the list. Example: /echo (enumsounds "VOICECOM !PUBLIC") will list all voicecom sounds that are for teams only. DOCREF enumsounds
695 lines
25 KiB
C++
695 lines
25 KiB
C++
// renderparticles.cpp
|
|
|
|
#include "cube.h"
|
|
|
|
static GLushort *hemiindices = NULL;
|
|
static vec *hemiverts = NULL;
|
|
static int heminumverts = 0, heminumindices = 0;
|
|
|
|
static void subdivide(int depth, int face);
|
|
|
|
static void genface(int depth, int i1, int i2, int i3)
|
|
{
|
|
int face = heminumindices; heminumindices += 3;
|
|
hemiindices[face] = i1;
|
|
hemiindices[face+1] = i2;
|
|
hemiindices[face+2] = i3;
|
|
subdivide(depth, face);
|
|
}
|
|
|
|
static void subdivide(int depth, int face)
|
|
{
|
|
if(depth-- <= 0) return;
|
|
int idx[6];
|
|
loopi(3) idx[i] = hemiindices[face+i];
|
|
loopi(3)
|
|
{
|
|
int vert = heminumverts++;
|
|
hemiverts[vert] = vec(hemiverts[idx[i]]).add(hemiverts[idx[(i+1)%3]]).normalize(); //push on to unit sphere
|
|
idx[3+i] = vert;
|
|
hemiindices[face+i] = vert;
|
|
}
|
|
subdivide(depth, face);
|
|
loopi(3) genface(depth, idx[i], idx[3+i], idx[3+(i+2)%3]);
|
|
}
|
|
|
|
//subdiv version wobble much more nicely than a lat/longitude version
|
|
static void inithemisphere(int hres, int depth)
|
|
{
|
|
const int tris = hres << (2*depth);
|
|
heminumverts = heminumindices = 0;
|
|
DELETEA(hemiverts);
|
|
DELETEA(hemiindices);
|
|
hemiverts = new vec[tris+1];
|
|
hemiindices = new GLushort[tris*3];
|
|
hemiverts[heminumverts++] = vec(0.0f, 0.0f, 1.0f); //build initial 'hres' sided pyramid
|
|
loopi(hres)
|
|
{
|
|
float a = PI2*float(i)/hres;
|
|
hemiverts[heminumverts++] = vec(cosf(a), sinf(a), 0.0f);
|
|
}
|
|
loopi(hres) genface(depth, 0, i+1, 1+(i+1)%hres);
|
|
}
|
|
|
|
GLuint createexpmodtex(int size, float minval)
|
|
{
|
|
uchar *data = new uchar[size*size], *dst = data;
|
|
loop(y, size) loop(x, size)
|
|
{
|
|
float dx = 2*float(x)/(size-1) - 1, dy = 2*float(y)/(size-1) - 1;
|
|
float z = max(0.0f, 1.0f - dx*dx - dy*dy);
|
|
if(minval) z = sqrtf(z);
|
|
else loopk(2) z *= z;
|
|
*dst++ = uchar(max(z, minval)*255);
|
|
}
|
|
GLuint tex = 0;
|
|
glGenTextures(1, &tex);
|
|
createtexture(tex, size, size, data, 3, true, false, GL_ALPHA);
|
|
delete[] data;
|
|
return tex;
|
|
}
|
|
|
|
static struct expvert
|
|
{
|
|
vec pos;
|
|
float u, v, s, t;
|
|
} *expverts = NULL;
|
|
|
|
static GLuint expmodtex[2] = {0, 0};
|
|
static GLuint lastexpmodtex = 0;
|
|
|
|
VARP(mtexplosion, 0, 1, 1);
|
|
|
|
void setupexplosion()
|
|
{
|
|
if(!hemiindices) inithemisphere(5, 2);
|
|
|
|
static int lastexpmillis = 0;
|
|
if(lastexpmillis != lastmillis || !expverts)
|
|
{
|
|
vec center = vec(13.0f, 2.3f, 7.1f);
|
|
lastexpmillis = lastmillis;
|
|
if(!expverts) expverts = new expvert[heminumverts];
|
|
loopi(heminumverts)
|
|
{
|
|
expvert &e = expverts[i];
|
|
vec &v = hemiverts[i];
|
|
e.u = v.x*0.5f + 0.001f*lastmillis;
|
|
e.v = v.y*0.5f + 0.001f*lastmillis;
|
|
e.s = v.x*0.5f + 0.5f;
|
|
e.t = v.y*0.5f + 0.5f;
|
|
float wobble = v.dot(center) + 0.002f*lastmillis;
|
|
wobble -= floor(wobble);
|
|
wobble = 1.0f + fabs(wobble - 0.5f)*0.5f;
|
|
e.pos = vec(v).mul(wobble);
|
|
}
|
|
}
|
|
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glVertexPointer(3, GL_FLOAT, sizeof(expvert), &expverts->pos);
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glTexCoordPointer(2, GL_FLOAT, sizeof(expvert), &expverts->u);
|
|
|
|
if(mtexplosion && maxtmus>=2)
|
|
{
|
|
setuptmu(0, "C * T", "= Ca");
|
|
|
|
glActiveTexture_(GL_TEXTURE1_ARB);
|
|
glClientActiveTexture_(GL_TEXTURE1_ARB);
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
setuptmu(1, "P * Ta x 4", "Pa * Ta x 4");
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glTexCoordPointer(2, GL_FLOAT, sizeof(expvert), &expverts->s);
|
|
|
|
glActiveTexture_(GL_TEXTURE0_ARB);
|
|
glClientActiveTexture_(GL_TEXTURE0_ARB);
|
|
|
|
if(!expmodtex[0]) expmodtex[0] = createexpmodtex(64, 0);
|
|
if(!expmodtex[1]) expmodtex[1] = createexpmodtex(64, 0.25f);
|
|
lastexpmodtex = 0;
|
|
}
|
|
}
|
|
|
|
void drawexplosion(bool inside, float r, float g, float b, float a)
|
|
{
|
|
if(mtexplosion && maxtmus>=2 && lastexpmodtex != expmodtex[inside ? 1 : 0])
|
|
{
|
|
glActiveTexture_(GL_TEXTURE1_ARB);
|
|
lastexpmodtex = expmodtex[inside ? 1 :0];
|
|
glBindTexture(GL_TEXTURE_2D, lastexpmodtex);
|
|
glActiveTexture_(GL_TEXTURE0_ARB);
|
|
}
|
|
loopi(!reflecting && inside ? 2 : 1)
|
|
{
|
|
glColor4f(r, g, b, i ? a/2 : a);
|
|
if(i)
|
|
{
|
|
glScalef(1, 1, -1);
|
|
glDepthFunc(GL_GEQUAL);
|
|
}
|
|
if(inside)
|
|
{
|
|
if(!reflecting)
|
|
{
|
|
glCullFace(GL_BACK);
|
|
glDrawElements(GL_TRIANGLES, heminumindices, GL_UNSIGNED_SHORT, hemiindices);
|
|
glCullFace(GL_FRONT);
|
|
}
|
|
glScalef(1, 1, -1);
|
|
}
|
|
glDrawElements(GL_TRIANGLES, heminumindices, GL_UNSIGNED_SHORT, hemiindices);
|
|
if(i) glDepthFunc(GL_LESS);
|
|
}
|
|
}
|
|
|
|
void cleanupexplosion()
|
|
{
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
|
|
if(mtexplosion && maxtmus>=2)
|
|
{
|
|
resettmu(0);
|
|
|
|
glActiveTexture_(GL_TEXTURE1_ARB);
|
|
glClientActiveTexture_(GL_TEXTURE1_ARB);
|
|
|
|
glDisable(GL_TEXTURE_2D);
|
|
resettmu(1);
|
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
|
|
glActiveTexture_(GL_TEXTURE0_ARB);
|
|
glClientActiveTexture_(GL_TEXTURE0_ARB);
|
|
}
|
|
}
|
|
|
|
struct particle { vec o, d; int fade, type; int millis; particle *next; };
|
|
particle *parlist[MAXPARTYPES], *parempty = NULL;
|
|
|
|
static Texture *parttex[7];
|
|
|
|
void particleinit()
|
|
{
|
|
loopi(MAXPARTYPES) parlist[i] = NULL;
|
|
|
|
parttex[0] = textureload("packages/misc/base.png");
|
|
parttex[1] = textureload("packages/misc/smoke.png");
|
|
parttex[2] = textureload("packages/misc/explosion.png");
|
|
parttex[3] = textureload("<decal>packages/misc/bullethole.png");
|
|
parttex[4] = textureload("packages/misc/blood.png");
|
|
parttex[5] = textureload("packages/misc/scorch.png");
|
|
parttex[6] = textureload("packages/misc/muzzleflash.jpg");
|
|
}
|
|
|
|
void cleanupparticles()
|
|
{
|
|
loopi(2) if(expmodtex[i]) { glDeleteTextures(1, &expmodtex[i]); expmodtex[i] = 0; }
|
|
}
|
|
|
|
void particlereset()
|
|
{
|
|
loopi(MAXPARTYPES)
|
|
{
|
|
while(parlist[i])
|
|
{
|
|
particle *p = parlist[i];
|
|
parlist[i] = p->next;
|
|
p->next = parempty;
|
|
parempty = p;
|
|
}
|
|
}
|
|
}
|
|
|
|
void newparticle(const vec &o, const vec &d, int fade, int type)
|
|
{
|
|
if(OUTBORD((int)o.x, (int)o.y)) return;
|
|
|
|
if(!parempty)
|
|
{
|
|
particle *ps = new particle[256];
|
|
loopi(256)
|
|
{
|
|
ps[i].next = parempty;
|
|
parempty = &ps[i];
|
|
}
|
|
}
|
|
|
|
particle *p = parempty;
|
|
parempty = p->next;
|
|
p->o = o;
|
|
p->d = d;
|
|
p->fade = fade;
|
|
p->type = type;
|
|
p->millis = lastmillis;
|
|
p->next = parlist[type];
|
|
parlist[type] = p;
|
|
}
|
|
|
|
const char *particletypenames[MAXPARTYPES + 1] = { "SPARK", "SMOKE", "ECLOSEST", "BLOOD", "DEMOTRACK", "FIREBALL", "SHOTLINE", "BULLETHOLE", "BLOODSTAIN", "SCORCH", // 0..9
|
|
"HUDMUZZLEFLASH", "MUZZLEFLASH", "ELIGHT", "ESPAWN", "EAMMO", "EPICKUP", "EMODEL", "ECARROT", "ELADDER", "EFLAG", "" }; // 10..19
|
|
|
|
static struct parttype { int type; float r, g, b; int gr, tex; float sz; } parttypes[MAXPARTYPES] =
|
|
{
|
|
{ PT_PART, 0.4f, 0.4f, 0.4f, 2, 0, 0.06f }, // yellow: sparks
|
|
{ PT_PART, 1.0f, 1.0f, 1.0f, 20, 1, 0.15f }, // grey: small smoke
|
|
{ PT_PART, 0.0f, 0.0f, 1.0f, 20, 0, 0.08f }, // blue: edit mode closest ent
|
|
{ PT_BLOOD, 0.5f, 0.0f, 0.0f, 1, 4, 0.3f }, // red: blood spats
|
|
{ PT_PART, 1.0f, 0.1f, 0.1f, 0, 1, 0.2f }, // red: demotrack
|
|
{ PT_FIREBALL, 1.0f, 0.5f, 0.5f, 0, 2, 7.0f }, // explosion fireball
|
|
{ PT_SHOTLINE, 1.0f, 1.0f, 0.7f, 0, -1, 0.0f }, // yellow: shotline
|
|
{ PT_BULLETHOLE, 1.0f, 1.0f, 1.0f, 0, 3, 0.3f }, // hole decal
|
|
|
|
{ PT_STAIN, 0.5f, 0.0f, 0.0f, 0, 4, 0.6f }, // red: blood stain
|
|
{ PT_DECAL, 1.0f, 1.0f, 1.0f, 0, 5, 1.5f }, // scorch decal
|
|
{ PT_HUDFLASH, 1.0f, 1.0f, 1.0f, 0, 6, 0.7f }, // hudgun muzzle flash
|
|
{ PT_FLASH, 1.0f, 1.0f, 1.0f, 0, 6, 0.7f }, // muzzle flash
|
|
{ PT_PART, 1.0f, 1.0f, 1.0f, 20, 0, 0.08f }, // white: edit mode ent type : light
|
|
{ PT_PART, 0.0f, 1.0f, 0.0f, 20, 0, 0.08f }, // green: edit mode ent type : spawn
|
|
{ PT_PART, 1.0f, 0.0f, 0.0f, 20, 0, 0.08f }, // red: edit mode ent type : ammo
|
|
{ PT_PART, 1.0f, 1.0f, 0.0f, 20, 0, 0.08f }, // yellow: edit mode ent type : pickup
|
|
{ PT_PART, 1.0f, 0.0f, 1.0f, 20, 0, 0.08f }, // magenta: edit mode ent type : model, sound
|
|
{ PT_PART, 1.0f, 0.5f, 0.2f, 20, 0, 0.08f }, // orange: edit mode ent type : "carrot"
|
|
{ PT_PART, 0.5f, 0.5f, 0.5f, 20, 0, 0.08f }, // grey: edit mode ent type : ladder, (pl)clip
|
|
{ PT_PART, 0.0f, 1.0f, 1.0f, 20, 0, 0.08f }, // turquoise: edit mode ent type : CTF-flag
|
|
};
|
|
|
|
VAR(particlesize, 20, 100, 500);
|
|
|
|
VARP(blood, 0, 1, 1);
|
|
VARP(bloodttl, 0, 10000, 30000);
|
|
|
|
void render_particles(int time, int typemask)
|
|
{
|
|
bool rendered = false;
|
|
for(int i = MAXPARTYPES-1; i>=0; i--) if(typemask&(1<<parttypes[i].type) && parlist[i])
|
|
{
|
|
if(!rendered)
|
|
{
|
|
rendered = true;
|
|
glDepthMask(GL_FALSE);
|
|
glEnable(GL_BLEND);
|
|
glDisable(GL_FOG);
|
|
}
|
|
|
|
parttype &pt = parttypes[i];
|
|
float sz = pt.sz*particlesize/100.0f;
|
|
|
|
if(pt.tex>=0) glBindTexture(GL_TEXTURE_2D, parttex[pt.tex]->id);
|
|
else glDisable(GL_TEXTURE_2D);
|
|
switch(pt.type)
|
|
{
|
|
case PT_HUDFLASH:
|
|
sethudgunperspective(true);
|
|
// fall through
|
|
case PT_FLASH:
|
|
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);
|
|
glColor3f(pt.r, pt.g, pt.b);
|
|
glBegin(GL_QUADS);
|
|
break;
|
|
|
|
case PT_PART:
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
|
glColor3f(pt.r, pt.g, pt.b);
|
|
glBegin(GL_QUADS);
|
|
break;
|
|
|
|
case PT_FIREBALL:
|
|
setupexplosion();
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
|
break;
|
|
|
|
case PT_SHOTLINE:
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glColor4f(pt.r, pt.g, pt.b, 0.5f);
|
|
glBegin(GL_LINES);
|
|
break;
|
|
|
|
case PT_DECAL:
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glBegin(GL_QUADS);
|
|
break;
|
|
|
|
case PT_BULLETHOLE:
|
|
glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA);
|
|
glBegin(GL_QUADS);
|
|
break;
|
|
|
|
case PT_BLOOD:
|
|
glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
|
|
glColor3f(1-pt.r, 1-pt.g, 1-pt.b);
|
|
glBegin(GL_QUADS);
|
|
break;
|
|
|
|
case PT_STAIN:
|
|
glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
|
|
glBegin(GL_QUADS);
|
|
break;
|
|
}
|
|
|
|
for(particle *p, **pp = &parlist[i]; (p = *pp);)
|
|
{
|
|
switch(pt.type)
|
|
{
|
|
case PT_HUDFLASH:
|
|
case PT_FLASH:
|
|
{
|
|
vec corners[4] =
|
|
{
|
|
vec(-camright.x+camup.x, -camright.y+camup.y, -camright.z+camup.z),
|
|
vec( camright.x+camup.x, camright.y+camup.y, camright.z+camup.z),
|
|
vec( camright.x-camup.x, camright.y-camup.y, camright.z-camup.z),
|
|
vec(-camright.x-camup.x, -camright.y-camup.y, -camright.z-camup.z)
|
|
};
|
|
loopk(4) corners[k].rotate(p->d.x, p->d.y, camdir).mul(sz*p->d.z).add(p->o);
|
|
|
|
glTexCoord2i(0, 1); glVertex3fv(corners[0].v);
|
|
glTexCoord2i(1, 1); glVertex3fv(corners[1].v);
|
|
glTexCoord2i(1, 0); glVertex3fv(corners[2].v);
|
|
glTexCoord2i(0, 0); glVertex3fv(corners[3].v);
|
|
|
|
xtraverts += 4;
|
|
break;
|
|
}
|
|
|
|
case PT_PART:
|
|
glTexCoord2i(0, 1); glVertex3f(p->o.x+(-camright.x+camup.x)*sz, p->o.y+(-camright.y+camup.y)*sz, p->o.z+(-camright.z+camup.z)*sz);
|
|
glTexCoord2i(1, 1); glVertex3f(p->o.x+( camright.x+camup.x)*sz, p->o.y+( camright.y+camup.y)*sz, p->o.z+( camright.z+camup.z)*sz);
|
|
glTexCoord2i(1, 0); glVertex3f(p->o.x+( camright.x-camup.x)*sz, p->o.y+( camright.y-camup.y)*sz, p->o.z+( camright.z-camup.z)*sz);
|
|
glTexCoord2i(0, 0); glVertex3f(p->o.x+(-camright.x-camup.x)*sz, p->o.y+(-camright.y-camup.y)*sz, p->o.z+(-camright.z-camup.z)*sz);
|
|
xtraverts += 4;
|
|
break;
|
|
|
|
case PT_FIREBALL:
|
|
{
|
|
sz = 1.0f + (pt.sz-1.0f)*min(p->fade, lastmillis-p->millis)/p->fade;
|
|
glPushMatrix();
|
|
glTranslatef(p->o.x, p->o.y, p->o.z);
|
|
bool inside = p->o.dist(camera1->o) <= sz*1.25f; //1.25 is max wobble scale
|
|
vec oc(p->o);
|
|
oc.sub(camera1->o);
|
|
if(reflecting && !refracting) oc.z = p->o.z - (waterlevel - 0.3f);
|
|
glRotatef(inside ? camera1->yaw - 180 : atan2(oc.y, oc.x)/RAD - 90, 0, 0, 1);
|
|
glRotatef((inside ? camera1->pitch : asin(oc.z/oc.magnitude())/RAD) - 90, 1, 0, 0);
|
|
|
|
glRotatef(lastmillis/7.0f, 0, 0, 1);
|
|
glScalef(-sz, sz, -sz);
|
|
drawexplosion(inside, pt.r, pt.g, pt.b, 1.0f-sz/pt.sz);
|
|
glPopMatrix();
|
|
xtraverts += heminumverts;
|
|
break;
|
|
}
|
|
|
|
case PT_SHOTLINE:
|
|
glVertex3f(p->o.x, p->o.y, p->o.z);
|
|
glVertex3f(p->d.x, p->d.y, p->d.z);
|
|
xtraverts += 2;
|
|
break;
|
|
|
|
case PT_DECAL:
|
|
{
|
|
sqr *s = S((int)p->o.x, (int)p->o.y);
|
|
glColor4f(s->r/127.5f, s->g/127.5f, s->b/127.5, max(0.0f, min((p->millis+p->fade - lastmillis)/1000.0f, 0.7f)));
|
|
vec dx(0, 0, 0), dy(0, 0, 0);
|
|
loopk(3) if(p->d[k]) { dx[(k+1)%3] = -1; dy[(k+2)%3] = p->d[k]; break; }
|
|
int o = detrnd((size_t)p, 11);
|
|
static const int tc[4][2] = { {0, 1}, {1, 1}, {1, 0}, {0, 0} };
|
|
glTexCoord2i(tc[o%4][0], tc[o%4][1]); glVertex3f(p->o.x+(-dx.x+dy.x)*pt.sz, p->o.y+(-dx.y+dy.y)*pt.sz, p->o.z+(-dx.z+dy.z)*pt.sz);
|
|
glTexCoord2i(tc[(o+1)%4][0], tc[(o+1)%4][1]); glVertex3f(p->o.x+( dx.x+dy.x)*pt.sz, p->o.y+( dx.y+dy.y)*pt.sz, p->o.z+( dx.z+dy.z)*pt.sz);
|
|
glTexCoord2i(tc[(o+2)%4][0], tc[(o+2)%4][1]); glVertex3f(p->o.x+( dx.x-dy.x)*pt.sz, p->o.y+( dx.y-dy.y)*pt.sz, p->o.z+( dx.z-dy.z)*pt.sz);
|
|
glTexCoord2i(tc[(o+3)%4][0], tc[(o+3)%4][1]); glVertex3f(p->o.x+(-dx.x-dy.x)*pt.sz, p->o.y+(-dx.y-dy.y)*pt.sz, p->o.z+(-dx.z-dy.z)*pt.sz);
|
|
xtraverts += 4;
|
|
break;
|
|
}
|
|
|
|
case PT_BULLETHOLE:
|
|
{
|
|
float blend = max(0.0f, min((p->millis+p->fade - lastmillis)/1000.0f, 1.0f));
|
|
glColor4f(pt.r*blend, pt.g*blend, pt.b*blend, blend);
|
|
vec dx(0, 0, 0), dy(0, 0, 0);
|
|
int tx = 0, ty = 1;
|
|
loopk(3) if(p->d[k])
|
|
{
|
|
dx[(k+1)%3] = -1; dy[(k+2)%3] = p->d[k];
|
|
if(k<2) { tx = k^1; ty = 2; }
|
|
break;
|
|
}
|
|
glTexCoord2f(0.5f+0.5f*(-dx[tx]+dy[tx]), 0.5f+0.5f*(-dx[ty]+dy[ty])); glVertex3f(p->o.x+(-dx.x+dy.x)*pt.sz, p->o.y+(-dx.y+dy.y)*pt.sz, p->o.z+(-dx.z+dy.z)*pt.sz);
|
|
glTexCoord2f(0.5f+0.5f*( dx[tx]+dy[tx]), 0.5f+0.5f*( dx[ty]+dy[ty])); glVertex3f(p->o.x+( dx.x+dy.x)*pt.sz, p->o.y+( dx.y+dy.y)*pt.sz, p->o.z+( dx.z+dy.z)*pt.sz);
|
|
glTexCoord2f(0.5f+0.5f*( dx[tx]-dy[tx]), 0.5f+0.5f*( dx[ty]-dy[ty])); glVertex3f(p->o.x+( dx.x-dy.x)*pt.sz, p->o.y+( dx.y-dy.y)*pt.sz, p->o.z+( dx.z-dy.z)*pt.sz);
|
|
glTexCoord2f(0.5f+0.5f*(-dx[tx]-dy[tx]), 0.5f+0.5f*(-dx[ty]-dy[ty])); glVertex3f(p->o.x+(-dx.x-dy.x)*pt.sz, p->o.y+(-dx.y-dy.y)*pt.sz, p->o.z+(-dx.z-dy.z)*pt.sz);
|
|
xtraverts += 4;
|
|
break;
|
|
}
|
|
|
|
case PT_BLOOD:
|
|
{
|
|
int n = detrnd((size_t)p, 4), o = detrnd((size_t)p, 11);;
|
|
float tx = 0.5f*(n&1), ty = 0.5f*((n>>1)&1), tsz = 0.5f;
|
|
static const int tc[4][2] = { {0, 1}, {1, 1}, {1, 0}, {0, 0} };
|
|
glTexCoord2f(tx+tsz*tc[o%4][0], ty+tsz*tc[o%4][1]); glVertex3f(p->o.x+(-camright.x+camup.x)*sz, p->o.y+(-camright.y+camup.y)*sz, p->o.z+(-camright.z+camup.z)*sz);
|
|
glTexCoord2f(tx+tsz*tc[(o+1)%4][0], ty+tsz*tc[(o+1)%4][1]); glVertex3f(p->o.x+( camright.x+camup.x)*sz, p->o.y+( camright.y+camup.y)*sz, p->o.z+( camright.z+camup.z)*sz);
|
|
glTexCoord2f(tx+tsz*tc[(o+2)%4][0], ty+tsz*tc[(o+2)%4][1]); glVertex3f(p->o.x+( camright.x-camup.x)*sz, p->o.y+( camright.y-camup.y)*sz, p->o.z+( camright.z-camup.z)*sz);
|
|
glTexCoord2f(tx+tsz*tc[(o+3)%4][0], ty+tsz*tc[(o+3)%4][1]); glVertex3f(p->o.x+(-camright.x-camup.x)*sz, p->o.y+(-camright.y-camup.y)*sz, p->o.z+(-camright.z-camup.z)*sz);
|
|
xtraverts += 4;
|
|
break;
|
|
}
|
|
|
|
case PT_STAIN:
|
|
{
|
|
float blend = max(0.0f, min((p->millis+p->fade - lastmillis)/1000.0f, 1.0f));
|
|
glColor3f(blend*(1-pt.r), blend*(1-pt.g), blend*(1-pt.b));
|
|
int n = detrnd((size_t)p, 4), o = detrnd((size_t)p, 11);
|
|
float tx = 0.5f*(n&1), ty = 0.5f*((n>>1)&1), tsz = 0.5f;
|
|
static const int tc[4][2] = { {0, 1}, {1, 1}, {1, 0}, {0, 0} };
|
|
vec dx(0, 0, 0), dy(0, 0, 0);
|
|
loopk(3) if(p->d[k]) { dx[(k+1)%3] = -1; dy[(k+2)%3] = p->d[k]; break; }
|
|
glTexCoord2f(tx+tsz*tc[o%4][0], ty+tsz*tc[o%4][1]); glVertex3f(p->o.x+(-dx.x+dy.x)*pt.sz, p->o.y+(-dx.y+dy.y)*pt.sz, p->o.z+(-dx.z+dy.z)*pt.sz);
|
|
glTexCoord2f(tx+tsz*tc[(o+1)%4][0], ty+tsz*tc[(o+1)%4][1]); glVertex3f(p->o.x+( dx.x+dy.x)*pt.sz, p->o.y+( dx.y+dy.y)*pt.sz, p->o.z+( dx.z+dy.z)*pt.sz);
|
|
glTexCoord2f(tx+tsz*tc[(o+2)%4][0], ty+tsz*tc[(o+2)%4][1]); glVertex3f(p->o.x+( dx.x-dy.x)*pt.sz, p->o.y+( dx.y-dy.y)*pt.sz, p->o.z+( dx.z-dy.z)*pt.sz);
|
|
glTexCoord2f(tx+tsz*tc[(o+3)%4][0], ty+tsz*tc[(o+3)%4][1]); glVertex3f(p->o.x+(-dx.x-dy.x)*pt.sz, p->o.y+(-dx.y-dy.y)*pt.sz, p->o.z+(-dx.z-dy.z)*pt.sz);
|
|
xtraverts += 4;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(time < 0) pp = &p->next;
|
|
else if(!p->fade || lastmillis-p->millis>p->fade)
|
|
{
|
|
*pp = p->next;
|
|
p->next = parempty;
|
|
parempty = p;
|
|
}
|
|
else
|
|
{
|
|
if(pt.gr) p->o.z -= ((lastmillis-p->millis)/3.0f)*time/(pt.gr*10000);
|
|
if(pt.type==PT_PART || pt.type==PT_BLOOD)
|
|
{
|
|
p->o.add(vec(p->d).mul(time/20000.0f));
|
|
if(OUTBORD((int)p->o.x, (int)p->o.y))
|
|
{
|
|
*pp = p->next;
|
|
p->next = parempty;
|
|
parempty = p;
|
|
continue;
|
|
}
|
|
}
|
|
if(pt.type==PT_BLOOD)
|
|
{
|
|
sqr *s = S((int)p->o.x, (int)p->o.y);
|
|
if(s->type==SPACE && p->o.z<=s->floor)
|
|
{
|
|
*pp = p->next;
|
|
p->next = parempty;
|
|
parempty = p;
|
|
newparticle(vec(p->o.x, p->o.y, s->floor+0.005f), vec(0, 0, 1), bloodttl, 8);
|
|
continue;
|
|
}
|
|
}
|
|
pp = &p->next;
|
|
}
|
|
}
|
|
|
|
switch(pt.type)
|
|
{
|
|
case PT_HUDFLASH:
|
|
glEnd();
|
|
sethudgunperspective(false);
|
|
break;
|
|
|
|
case PT_PART:
|
|
case PT_SHOTLINE:
|
|
case PT_DECAL:
|
|
case PT_BULLETHOLE:
|
|
case PT_BLOOD:
|
|
case PT_STAIN:
|
|
case PT_FLASH:
|
|
glEnd();
|
|
break;
|
|
|
|
case PT_FIREBALL:
|
|
cleanupexplosion();
|
|
break;
|
|
}
|
|
if(pt.tex<0) glEnable(GL_TEXTURE_2D);
|
|
}
|
|
|
|
if(rendered)
|
|
{
|
|
glEnable(GL_FOG);
|
|
glDisable(GL_BLEND);
|
|
glDepthMask(GL_TRUE);
|
|
}
|
|
}
|
|
|
|
void particle_emit(int type, int *args, int basetime, int seed, const vec &p)
|
|
{
|
|
if(type<0 || type>=MAXPARTYPES) return;
|
|
parttype &pt = parttypes[type];
|
|
if(pt.type==PT_FIREBALL) particle_fireball(type, p);
|
|
else if(pt.type==PT_FLASH || pt.type==PT_HUDFLASH)
|
|
{
|
|
if(lastmillis - basetime < args[0])
|
|
particle_flash(type, args[1]>0 ? args[1]/100.0f : 1.0f, seed%360, p);
|
|
}
|
|
else particle_splash(type, args[0], args[1], p);
|
|
}
|
|
|
|
void particle_flash(int type, float scale, float angle, const vec &p)
|
|
{
|
|
angle *= RAD;
|
|
newparticle(p, vec(cosf(angle), sinf(angle), scale), 0, type);
|
|
}
|
|
|
|
void particle_splash(int type, int num, int fade, const vec &p)
|
|
{
|
|
if(parttypes[type].type==PT_BLOOD && !blood) return;
|
|
if(fade == 0) fade = 1;
|
|
loopi(num)
|
|
{
|
|
const int radius = 150;
|
|
int x, y, z;
|
|
do
|
|
{
|
|
x = rnd(radius*2)-radius;
|
|
y = rnd(radius*2)-radius;
|
|
z = rnd(radius*2)-radius;
|
|
}
|
|
while(x*x+y*y+z*z>radius*radius);
|
|
vec d((float)x, (float)y, (float)z);
|
|
newparticle(p, d, rnd(fade*3), type);
|
|
}
|
|
}
|
|
|
|
void particle_cube(int type, int num, int fade, int x, int y)
|
|
{
|
|
sqr *s = S(x, y);
|
|
const int fc = s->ceil - s->floor + (s->vdelta + 3) / 4;
|
|
const float floor = s->floor - (s->type == FHF ? float(s->vdelta) / 4 : 0);
|
|
if(SOLID(s) || fc < 1) return;
|
|
loopi(num * fc / 12)
|
|
{
|
|
vec p(float(rnd(100)) / 100.0 + x, float(rnd(100)) / 100.0 + y, float(rnd(100) * fc) / 100 + floor), d(0, 0, 0);
|
|
newparticle(p, d, rnd(fade*3), type);
|
|
}
|
|
}
|
|
|
|
VARP(maxtrail, 1, 500, 10000);
|
|
|
|
void particle_trail(int type, int fade, const vec &s, const vec &e)
|
|
{
|
|
vec v;
|
|
float d = e.dist(s, v);
|
|
int steps = clamp(int(d*2), 1, maxtrail);
|
|
v.div(steps);
|
|
vec p = s;
|
|
loopi(steps)
|
|
{
|
|
p.add(v);
|
|
vec tmp((float)(rnd(11)-5), (float)(rnd(11)-5), (float)(rnd(11)-5));
|
|
newparticle(p, tmp, rnd(fade)+fade, type);
|
|
}
|
|
}
|
|
|
|
void particle_fireball(int type, const vec &o)
|
|
{
|
|
newparticle(o, vec(0, 0, 0), (int)((parttypes[type].sz-1.0f)*100.0f), type);
|
|
}
|
|
|
|
VARP(bulletbouncesound, 0, 1, 1);
|
|
VARP(bullethole, 0, 1, 1);
|
|
VARP(bulletholettl, 0, 10000, 30000);
|
|
VARP(bulletbouncesoundrad, 0, 15, 1000);
|
|
|
|
bool addbullethole(dynent *d, const vec &from, const vec &to, float radius, bool noisy)
|
|
{
|
|
if(!bulletholettl || !bullethole) return false;
|
|
vec surface, ray(to);
|
|
ray.sub(from);
|
|
ray.normalize();
|
|
float dist = raycube(from, ray, surface), mag = to.dist(from);
|
|
if(surface.iszero() || (radius>0 && (dist < mag-radius || dist > mag+radius))) return false;
|
|
vec o(from);
|
|
o.add(ray.mul(dist));
|
|
o.add(vec(surface).normalize().mul(0.01f));
|
|
newparticle(o, surface, bulletholettl, 7);
|
|
if(noisy && bulletbouncesound && bulletbouncesoundrad && d!=player1 && o.dist(camera1->o) <= bulletbouncesoundrad)
|
|
{
|
|
audiomgr.playsound(o.z < waterlevel ? S_BULLETWATERHIT : S_BULLETHIT, &o, SP_LOW);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
VARP(scorch, 0, 1, 1);
|
|
VARP(scorchttl, 0, 10000, 30000);
|
|
|
|
bool addscorchmark(vec &o, float radius)
|
|
{
|
|
if(!scorchttl || !scorch) return false;
|
|
sqr *s = S((int)o.x, (int)o.y);
|
|
if(s->type!=SPACE || o.z-s->floor>radius) return false;
|
|
newparticle(vec(o.x, o.y, s->floor+0.02f), vec(0, 0, 1), scorchttl, 9);
|
|
return true;
|
|
}
|
|
|
|
VARP(shotline, 0, 1, 1);
|
|
VARP(shotlinettl, 0, 75, 10000);
|
|
VARP(bulletairsound, 0, 1, 1);
|
|
VARP(bulletairsoundrad, 0, 15, 1000);
|
|
VARP(bulletairsoundsourcerad, 0, 8, 1000);
|
|
VARP(bulletairsounddestrad, 0, 8, 1000);
|
|
|
|
void addshotline(dynent *pl, const vec &from, const vec &to)
|
|
{
|
|
if(pl == player1 || !shotlinettl || !shotline) return;
|
|
bool fx = shotlinettl > 0 && (player1->isspectating() || !multiplayer(NULL)); // allow fx only in spect mode and locally
|
|
if(rnd(3) && !fx) return; // show all shotlines when fx enabled
|
|
|
|
int start = (camera1->o.dist(to) <= 10.0f) ? 8 : 5;
|
|
vec unitv;
|
|
float dist = to.dist(from, unitv);
|
|
if(dist < 1) return;
|
|
unitv.div(dist);
|
|
|
|
// shotline visuals
|
|
vec o = unitv;
|
|
o.mul(dist/10+start).add(from);
|
|
vec d = unitv;
|
|
d.mul(dist/10*-(10-start-2)).add(to);
|
|
newparticle(o, d, fx ? shotlinettl : min(75, shotlinettl), 6);
|
|
|
|
// shotline sound fx
|
|
if(!bulletairsoundrad) return;
|
|
vec fromuv, touv;
|
|
float fd = camera1->o.dist(from, fromuv);
|
|
float td = camera1->o.dist(to, touv);
|
|
if(fd <= (float)bulletairsoundsourcerad || td <= (float)bulletairsounddestrad) return; // ignore nearly fired/detonated shots
|
|
vec soundpos(unitv);
|
|
soundpos.mul(fd/(fd+td)*dist);
|
|
soundpos.add(from);
|
|
if(!bulletairsound || soundpos.dist(camera1->o) > bulletairsoundrad) return; // outside player radius
|
|
audiomgr.playsound(rnd(2) ? S_BULLETAIR1 : S_BULLETAIR2, &soundpos, SP_LOW);
|
|
}
|
|
|