diff --git a/packages/misc/muzzleflash.jpg b/packages/misc/muzzleflash.jpg new file mode 100644 index 00000000..9742b4a3 Binary files /dev/null and b/packages/misc/muzzleflash.jpg differ diff --git a/packages/models/weapons/assault/md3.cfg b/packages/models/weapons/assault/md3.cfg index fd64e1c3..13275100 100644 --- a/packages/models/weapons/assault/md3.cfg +++ b/packages/models/weapons/assault/md3.cfg @@ -4,3 +4,4 @@ md3skin hands skin_hands.jpg md3anim "gun idle" 0 1 md3anim "gun shoot" 8 8 md3anim "gun reload" 0 1 +md3emit tag_muzzle 10 50 diff --git a/source/src/cube.h b/source/src/cube.h index 2c3a075c..6701f6df 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -31,7 +31,7 @@ extern vector players; // all the other clients (in multiplayer extern vector bounceents; extern bool editmode; extern vector ents; // map entities -extern vec worldpos, camup, camright; // current target of the crosshair in the world +extern vec worldpos, camup, camright, camdir; // current target of the crosshair in the world extern vec hitpos; extern int lastmillis, totalmillis; // last time extern int curtime; // current frame time diff --git a/source/src/geom.h b/source/src/geom.h index 36e5dbd6..df5e7545 100644 --- a/source/src/geom.h +++ b/source/src/geom.h @@ -38,6 +38,34 @@ struct vec float distxy(const vec &e) const { float dx = e.x - x, dy = e.y - y; return sqrtf(dx*dx + dy*dy); } bool reject(const vec &o, float max) const { return x>o.x+max || xo.y+max || y fabs(d.y) ? (fabs(d.x) > fabs(d.z) ? 0 : 2) : (fabs(d.y) > fabs(d.z) ? 1 : 2); + v[i] = d[(i+1)%3]; + v[(i+1)%3] = -d[i]; + v[(i+2)%3] = 0; + } }; struct ivec diff --git a/source/src/md2.h b/source/src/md2.h index 042342b9..f7e76cf5 100644 --- a/source/src/md2.h +++ b/source/src/md2.h @@ -195,7 +195,7 @@ struct md2 : vertmodel if(!cullface) glEnable(GL_CULL_FACE); else if(anim&ANIM_MIRROR) glCullFace(GL_FRONT); - if(a) for(int i = 0; a[i].name; i++) + if(a) for(int i = 0; a[i].type; i++) { vertmodel *m = (vertmodel *)a[i].m; if(!m) continue; diff --git a/source/src/md3.h b/source/src/md3.h index 90a0a676..5f0be957 100644 --- a/source/src/md3.h +++ b/source/src/md3.h @@ -154,7 +154,7 @@ struct md3 : vertmodel { if(!loaded) return; - if(a) for(int i = 0; a[i].name; i++) + if(a) for(int i = 0; a[i].type; i++) { vertmodel *m = (vertmodel *)a[i].m; if(!m) continue;; @@ -184,7 +184,7 @@ struct md3 : vertmodel if(!cullface) glEnable(GL_CULL_FACE); else if(anim&ANIM_MIRROR) glCullFace(GL_FRONT); - if(a) for(int i = 0; a[i].name; i++) + if(a) for(int i = 0; a[i].type; i++) { switch(a[i].type) { @@ -285,8 +285,16 @@ void md3link(char *parentno, char *childno, char *tagname) if(!loadingmd3->parts[parent]->link(loadingmd3->parts[child], tagname)) conoutf("could not link model %s", loadingmd3->loadname); } +void md3emit(char *tag, char *type, char *arg1, char *arg2) +{ + if(!loadingmd3 || loadingmd3->parts.empty()) { conoutf("not loading an md3"); return; }; + md3::part &mdl = *loadingmd3->parts.last(); + if(!mdl.addemitter(tag, ATOI(type), ATOI(arg1), ATOI(arg2))) { conoutf("could not find tag %s", tag); return; } +} + COMMAND(md3load, ARG_1STR); COMMAND(md3skin, ARG_3STR); COMMAND(md3anim, ARG_4STR); COMMAND(md3link, ARG_3STR); +COMMAND(md3emit, ARG_4STR); diff --git a/source/src/menus.cpp b/source/src/menus.cpp index a6813c51..a0c1fdec 100644 --- a/source/src/menus.cpp +++ b/source/src/menus.cpp @@ -636,7 +636,7 @@ void rendermenumdl() s_sprintfd(skin)("packages/models/%s.jpg", m.mdl); tex = -(int)textureload(skin)->id; } - modelattach a[2] = { { NULL }, { NULL } }; + modelattach a[2]; if(isplayermodel) { a[0].name = "weapons/subgun/world"; diff --git a/source/src/model.h b/source/src/model.h index 7b2e3781..8422f855 100644 --- a/source/src/model.h +++ b/source/src/model.h @@ -9,6 +9,7 @@ enum { ANIM_IDLE = 0, ANIM_RUN, ANIM_ATTACK, ANIM_PAIN, ANIM_JUMP, ANIM_LAND, AN #define ANIM_MIRROR (1<<13) #define ANIM_NOSKIN (1<<14) #define ANIM_TRANSLUCENT (1<<15) +#define ANIM_PARTICLE (1<<16) struct animstate // used for animation blending of animated characters { @@ -23,7 +24,7 @@ struct animstate // used for animation blending o enum { MDL_MD2 = 1, MDL_MD3 }; -enum { MDL_ATTACH_VWEP = 0 }; +enum { MDL_ATTACH_NONE = 0, MDL_ATTACH_VWEP }; struct model; struct modelattach @@ -32,6 +33,8 @@ struct modelattach int type, anim, basetime; float speed; model *m; + + modelattach() : name(NULL), type(MDL_ATTACH_NONE) {} }; struct dynent; diff --git a/source/src/protos.h b/source/src/protos.h index a6af0903..b28baf5b 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -175,6 +175,7 @@ extern void blendbox(int x1, int y1, int x2, int y2, bool border, int tex = -1, extern void quad(GLuint tex, float x, float y, float s, float tx, float ty, float tsx, float tsy = 0); extern void quad(GLuint tex, vec &c1, vec &c2, float tx, float ty, float tsx, float tsy); extern void circle(GLuint tex, float x, float y, float r, float tx, float ty, float tr, int subdiv = 32); +extern void sethudgunperspective(bool on); extern void gl_drawframe(int w, int h, float changelod, float curfps); extern void clearminimap(); extern void rendercursor(int x, int y, int w); @@ -331,6 +332,8 @@ extern void computeraytable(float vx, float vy); extern int isoccluded(float vx, float vy, float cx, float cy, float csize); // main +extern SDL_Surface *screen; + extern void keyrepeat(bool on); extern bool initwarning(); extern bool firstrun; @@ -390,8 +393,10 @@ extern void updatedmgindicator(vec &attack); // renderparticles extern void particleinit(); extern void particlereset(); +extern void particle_flash(int type, int fade, vec &p); extern void particle_splash(int type, int num, int fade, vec &p); extern void particle_trail(int type, int fade, vec &from, vec &to); +extern void particle_emit(int type, int *args, vec &p); extern void particle_fireball(int type, vec &o); extern void addshotline(dynent *d, vec &from, vec &to); extern bool addbullethole(vec &from, vec &to, float radius = 1, bool noisy = true); diff --git a/source/src/rendergl.cpp b/source/src/rendergl.cpp index 1600bf19..ddd08bae 100644 --- a/source/src/rendergl.cpp +++ b/source/src/rendergl.cpp @@ -290,6 +290,8 @@ void rendercursor(int x, int y, int w) VARP(fov, 90, 100, 120); VAR(fog, 64, 180, 1024); VAR(fogcolour, 0, 0x8099B3, 0xFFFFFF); +float fovy, aspect; +int farplane; physent *camera1 = NULL; @@ -609,25 +611,29 @@ int xtraverts; VARP(hudgun, 0, 1, 1); +void sethudgunperspective(bool on) +{ + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + if(on) + { + glScalef(1, 1, 0.5f); // fix hudugns colliding with map geometry + gluPerspective((float)100.0f*screen->h/screen->w, aspect, 0.3f, farplane); // fov fixed at 100° + } + else gluPerspective((float)fov*screen->h/screen->w, aspect, 0.15f, farplane); + glMatrixMode(GL_MODELVIEW); +} + void drawhudgun(int w, int h, float aspect, int farplane) { if(scoped && player1->weaponsel->type==GUN_SNIPER) return; - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - glScalef(1, 1, 0.5f); // fix hudugns colliding with map geometry - - gluPerspective((float)100.0f*h/w, aspect, 0.3f, farplane); // fov fixed at 100° - glMatrixMode(GL_MODELVIEW); + sethudgunperspective(true); if(hudgun && player1->state==CS_ALIVE) player1->weaponsel->renderhudmodel(); rendermenumdl(); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - gluPerspective((float)fov*h/w, aspect, 0.15f, farplane); - glMatrixMode(GL_MODELVIEW); + sethudgunperspective(false); } bool outsidemap(physent *pl) @@ -641,8 +647,8 @@ bool outsidemap(physent *pl) float cursordepth = 0.9f; GLint viewport[4]; -GLdouble mm[16], pm[16]; -vec worldpos, camup, camright; +GLdouble mm[16], pm[16], invmm[16]; +vec worldpos, camdir, camup, camright; void readmatrices() { @@ -651,6 +657,20 @@ void readmatrices() glGetDoublev(GL_PROJECTION_MATRIX, pm); camright = vec(float(mm[0]), float(mm[4]), float(mm[8])); camup = vec(float(mm[1]), float(mm[5]), float(mm[9])); + camdir = vec(float(-mm[2]), float(-mm[6]), float(-mm[10])); + + loopi(3) + { + loopj(3) invmm[i*4 + j] = mm[i + j*4]; + invmm[i*4 + 3] = 0; + } + loopi(3) + { + double c = 0; + loopj(3) c -= mm[i*4 + j] * mm[12 + j]; + invmm[12 + i] = c; + } + invmm[15] = 1; } // stupid function to cater for stupid ATI linux drivers that return incorrect depth values @@ -683,8 +703,8 @@ void gl_drawframe(int w, int h, float changelod, float curfps) recomputecamera(); float hf = hdr.waterlevel-0.3f; - float fovy = (float)fov*h/w; - float aspect = w/(float)h; + fovy = (float)fov*h/w; + aspect = w/(float)h; bool underwater = camera1->o.zweaponsel==d->lastattackweapon && lastmillis-d->lastaction<300) { anim = d->crouching ? ANIM_CROUCH_ATTACK : ANIM_ATTACK; speed = 300.0f/8; basetime = d->lastaction; } else if(!d->move && !d->strafe) { anim = (d->crouching ? ANIM_CROUCH_IDLE : ANIM_IDLE)|ANIM_LOOP; } else { anim = (d->crouching ? ANIM_CROUCH_WALK : ANIM_RUN)|ANIM_LOOP; speed = 1860/d->maxspeed; } - modelattach a[2] = { { NULL }, { NULL } }; + modelattach a[2]; if(vwepname) { a[0].name = vwepname; diff --git a/source/src/renderparticles.cpp b/source/src/renderparticles.cpp index 8c7c79d5..49d841c4 100644 --- a/source/src/renderparticles.cpp +++ b/source/src/renderparticles.cpp @@ -185,12 +185,12 @@ void cleanupexplosion() } } -#define MAXPARTYPES 10 +#define MAXPARTYPES 11 struct particle { vec o, d; int fade, type; int millis; particle *next; }; particle *parlist[MAXPARTYPES], *parempty = NULL; -static Texture *parttex[6]; +static Texture *parttex[7]; void particleinit() { @@ -202,6 +202,7 @@ void particleinit() parttex[3] = textureload("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() @@ -256,7 +257,8 @@ enum PT_DECAL, PT_BULLETHOLE, PT_BLOOD, - PT_STAIN + PT_STAIN, + PT_FLASH }; static struct parttype { int type; float r, g, b; int gr, tex; float sz; } parttypes[] = @@ -271,6 +273,7 @@ static struct parttype { int type; float r, g, b; int gr, tex; float sz; } partt { 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_FLASH, 1.0f, 1.0f, 1.0f, 0, 6, 0.7f }, // muzzle flash }; VAR(particlesize, 20, 100, 500); @@ -280,6 +283,8 @@ VARP(bloodttl, 0, 10000, 30000); void render_particles(int time) { + extern float aspect; + extern int farplane, fov; bool rendered = false; for(int i = MAXPARTYPES-1; i>=0; i--) if(parlist[i]) { @@ -298,6 +303,13 @@ void render_particles(int time) else glDisable(GL_TEXTURE_2D); switch(pt.type) { + case PT_FLASH: + sethudgunperspective(true); + 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); @@ -341,6 +353,26 @@ void render_particles(int time) { switch(pt.type) { + 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).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); @@ -481,6 +513,11 @@ void render_particles(int time) switch(pt.type) { + case PT_FLASH: + glEnd(); + sethudgunperspective(false); + break; + case PT_PART: case PT_SHOTLINE: case PT_DECAL: @@ -505,6 +542,20 @@ void render_particles(int time) } } +void particle_emit(int type, int *args, 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) particle_flash(type, args[0], p); + else particle_splash(type, args[0], args[1], p); +} + +void particle_flash(int type, int fade, vec &p) +{ + float angle = rnd(360)*RAD; + newparticle(p, vec(cosf(angle), sinf(angle), 0), fade, type); +} void particle_splash(int type, int num, int fade, vec &p) { diff --git a/source/src/vertmodel.h b/source/src/vertmodel.h index 56c62bcd..2ddc1c04 100644 --- a/source/src/vertmodel.h +++ b/source/src/vertmodel.h @@ -188,6 +188,16 @@ struct vertmodel : model float speed; }; + struct particleemitter + { + int type, args[2]; + + particleemitter() : type(-1) + { + memset(args, 0, sizeof(args)); + } + }; + struct tag { char *name; @@ -208,10 +218,11 @@ struct vertmodel : model part **links; tag *tags; int numtags; + particleemitter *emitters; GLuint *shadows; float shadowrad; - part() : filename(NULL), anims(NULL), links(NULL), tags(NULL), numtags(0), shadows(NULL), shadowrad(0) {} + part() : filename(NULL), anims(NULL), links(NULL), tags(NULL), numtags(0), emitters(NULL), shadows(NULL), shadowrad(0) {} virtual ~part() { DELETEA(filename); @@ -219,6 +230,7 @@ struct vertmodel : model DELETEA(anims); DELETEA(links); DELETEA(tags); + DELETEA(emitters); if(shadows) glDeleteTextures(numframes, shadows); DELETEA(shadows); } @@ -243,6 +255,20 @@ struct vertmodel : model return false; } + bool addemitter(const char *tag, int type, int arg1 = 0, int arg2 = 0) + { + loopi(numtags) if(!strcmp(tags[i].name, tag)) + { + if(!emitters) emitters = new particleemitter[numtags]; + particleemitter &p = emitters[i]; + p.type = type; + p.args[0] = arg1; + p.args[1] = arg2; + return true; + } + return false; + } + void scaleverts(const float scale, const vec &translate) { loopv(meshes) @@ -274,6 +300,34 @@ struct vertmodel : model as.range = 1; } + void gentagmatrix(anpos &cur, anpos *prev, float ai_t, int i, GLfloat *matrix) + { + tag *tag1 = &tags[cur.fr1*numtags+i]; + tag *tag2 = &tags[cur.fr2*numtags+i]; + #define ip(p1, p2, t) (p1+t*(p2-p1)) + #define ip_ai_tag(c) ip( ip( tag1p->c, tag2p->c, prev->t), ip( tag1->c, tag2->c, cur.t), ai_t) + if(prev) + { + tag *tag1p = &tags[prev->fr1 * numtags + i]; + tag *tag2p = &tags[prev->fr2 * numtags + i]; + loopj(3) matrix[j] = ip_ai_tag(transform[0][j]); // transform + loopj(3) matrix[4 + j] = ip_ai_tag(transform[1][j]); + loopj(3) matrix[8 + j] = ip_ai_tag(transform[2][j]); + loopj(3) matrix[12 + j] = ip_ai_tag(pos[j]); // position + } + else + { + loopj(3) matrix[j] = ip(tag1->transform[0][j], tag2->transform[0][j], cur.t); // transform + loopj(3) matrix[4 + j] = ip(tag1->transform[1][j], tag2->transform[1][j], cur.t); + loopj(3) matrix[8 + j] = ip(tag1->transform[2][j], tag2->transform[2][j], cur.t); + loopj(3) matrix[12 + j] = ip(tag1->pos[j], tag2->pos[j], cur.t); // position + } + #undef ip_ai_tag + #undef ip + matrix[3] = matrix[7] = matrix[11] = 0.0f; + matrix[15] = 1.0f; + } + bool calcanimstate(int anim, int varseed, float speed, int basetime, dynent *d, animstate &as) { as.anim = anim; @@ -348,39 +402,32 @@ struct vertmodel : model loopv(meshes) meshes[i]->render(as, cur, doai ? &prev : NULL, ai_t); - loopi(numtags) if(links[i]) // render the linked models - interpolate rotation and position of the 'link-tags' + loopi(numtags) if(links[i] || (anim&ANIM_PARTICLE && emitters && emitters[i].type>=0)) // render the linked models - interpolate rotation and position of the 'link-tags' { - part *link = links[i]; - GLfloat matrix[16]; - tag *tag1 = &tags[cur.fr1*numtags+i]; - tag *tag2 = &tags[cur.fr2*numtags+i]; - #define ip(p1, p2, t) (p1+t*(p2-p1)) - #define ip_ai_tag(c) ip( ip( tag1p->c, tag2p->c, prev.t), ip( tag1->c, tag2->c, cur.t), ai_t) - if(doai) + gentagmatrix(cur, doai ? &prev : NULL, ai_t, i, matrix); + + part *link = links[i]; + if(link) { - tag *tag1p = &tags[prev.fr1 * numtags + i]; - tag *tag2p = &tags[prev.fr2 * numtags + i]; - loopj(3) matrix[j] = ip_ai_tag(transform[0][j]); // transform - loopj(3) matrix[4 + j] = ip_ai_tag(transform[1][j]); - loopj(3) matrix[8 + j] = ip_ai_tag(transform[2][j]); - loopj(3) matrix[12 + j] = ip_ai_tag(pos[j]); // position + glPushMatrix(); + glMultMatrixf(matrix); + link->render(anim, varseed, speed, basetime, d); + glPopMatrix(); } - else + + if(anim&ANIM_PARTICLE && emitters && emitters[i].type>=0) { - loopj(3) matrix[j] = ip(tag1->transform[0][j], tag2->transform[0][j], cur.t); // transform - loopj(3) matrix[4 + j] = ip(tag1->transform[1][j], tag2->transform[1][j], cur.t); - loopj(3) matrix[8 + j] = ip(tag1->transform[2][j], tag2->transform[2][j], cur.t); - loopj(3) matrix[12 + j] = ip(tag1->pos[j], tag2->pos[j], cur.t); // position + vec tagpos(matrix[12], matrix[13], matrix[14]); + GLdouble mm[16]; + glGetDoublev(GL_MODELVIEW_MATRIX, mm); + vec eyepos; + loopk(3) eyepos[k] = tagpos.x*mm[0+k] + tagpos.y*mm[4+k] + tagpos.z*mm[8+k] + mm[12+k]; + extern GLdouble invmm[16]; + vec worldpos; + loopk(3) worldpos[k] = eyepos.x*invmm[0+k] + eyepos.y*invmm[4+k] + eyepos.z*invmm[8+k] + invmm[12+k]; + particle_emit(emitters[i].type, emitters[i].args, worldpos); } - #undef ip_ai_tag - #undef ip - matrix[3] = matrix[7] = matrix[11] = 0.0f; - matrix[15] = 1.0f; - glPushMatrix(); - glMultMatrixf(matrix); - link->render(anim, varseed, speed, basetime, d); - glPopMatrix(); } } diff --git a/source/src/weapon.cpp b/source/src/weapon.cpp index bb238bd9..6325d65e 100644 --- a/source/src/weapon.cpp +++ b/source/src/weapon.cpp @@ -628,12 +628,14 @@ void weapon::renderhudmodel(int lastaction, int index) if(!intermission) wm.calcmove(unitv, lastaction); s_sprintfd(path)("weapons/%s", info.modelname); static int lastanim[2], lastswitch[2]; - if(lastanim[index]!=(wm.anim|(type<<16))) + bool emit = false; + if(lastanim[index]!=(wm.anim|(type<<24))) { - lastanim[index] = wm.anim|(type<<16); + if((lastanim[index]&ANIM_INDEX)!=ANIM_GUN_SHOOT && (wm.anim&ANIM_INDEX)==ANIM_GUN_SHOOT) emit = true; + lastanim[index] = wm.anim|(type<<24); lastswitch[index] = lastmillis; } - rendermodel(path, wm.anim|(index ? ANIM_MIRROR : 0), 0, 0, wm.pos, player1->yaw+90, player1->pitch+wm.k_rot, 40.0f, lastswitch[index], NULL, NULL, 1.28f); + rendermodel(path, wm.anim|(index ? ANIM_MIRROR : 0)|(emit ? ANIM_PARTICLE : 0), 0, 0, wm.pos, player1->yaw+90, player1->pitch+wm.k_rot, 40.0f, lastswitch[index], NULL, NULL, 1.28f); } void weapon::updatetimers()