From 0f7fc2149ee78e75a4b3f15abf1c7b390a8f5821 Mon Sep 17 00:00:00 2001 From: driAn Date: Sun, 19 Feb 2006 13:13:22 +0000 Subject: [PATCH] md3 git-svn-id: svn://svn.code.sf.net/p/actiongame/code/trunk/ac@175 e016b1c5-ed68-44aa-a2f7-31cac88cffde --- source/src/clientextras.cpp | 9 +- source/src/clientgame.cpp | 5 + source/src/cube.h | 355 +----------------------------------- source/src/rendermd3.cpp | 150 +++++---------- 4 files changed, 60 insertions(+), 459 deletions(-) diff --git a/source/src/clientextras.cpp b/source/src/clientextras.cpp index 8b14ed77..adcdd23a 100644 --- a/source/src/clientextras.cpp +++ b/source/src/clientextras.cpp @@ -56,8 +56,8 @@ void renderclients() if(strcmp(d->name, "dummy") == 0) { d->gunselect = player1->gunselect; - d->state = CS_ALIVE; d->moving = true; + d->health != 1 ? d->state=CS_ALIVE : d->state=CS_DEAD; }; if (!strcmp(d->team,"CT")) @@ -78,9 +78,9 @@ void renderclients() renderclient(player1, true, "playermodels/counterterrorist", false,1.4f); else renderclient(player1, true, "playermodels/terrorist", false, 1.4f); - - sprintf_sd(vwep)("weapons/%s/world", hudgunnames[d->gunselect]); - renderclient(d, isteam(player1->team, d->team), vwep, true, 1.4f); + + sprintf_sd(vwep)("weapons/%s/world", hudgunnames[player1->gunselect]); + renderclient(player1, true, vwep, true, 1.4f); }; }; @@ -92,6 +92,7 @@ void spawn_dummy() strcpy(d->team, player1->team); d->gunselect = player1->gunselect; d->monsterstate = M_NONE; + d->state = CS_ALIVE; strcpy(d->name, "dummy"); }; COMMAND(spawn_dummy, ARG_NONE); diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index ea2e2267..9f09a28d 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -1033,6 +1033,11 @@ void selfdamage(int damage, int actor, dynent *act) }; +void killdummies() +{ + loopv(players) if(strcmp(players[i]->name, "dummy") == 0) { players[i]->lastaction = lastmillis; players[i]->health=1; }; +}; COMMAND(killdummies, ARG_NONE); + void timeupdate(int timeremain) diff --git a/source/src/cube.h b/source/src/cube.h index 1d4ed058..c075076c 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -1,701 +1,350 @@ /// one big bad include file for the whole engine... nasty! - - #include "tools.h" - - enum // block types, order matters! - { - SOLID = 0, // entirely solid cube [only specifies wtex] - CORNER, // half full corner of a wall - FHF, // floor heightfield using neighbour vdelta values - CHF, // idem ceiling - SPACE, // entirely empty cube - SEMISOLID, // generated by mipmapping - MAXTYPE - }; - - struct sqr - { - uchar type; // one of the above - char floor, ceil; // height, in cubes - uchar wtex, ftex, ctex; // wall/floor/ceil texture ids - uchar r, g, b; // light value at upper left vertex - uchar vdelta; // vertex delta, used for heightfield cubes - char defer; // used in mipmapping, when true this cube is not a perfect mip - char occluded; // true when occluded - uchar utex; // upper wall tex id - uchar tag; // used by triggers - }; - - enum // hardcoded texture numbers - { - DEFAULT_SKY = 0, - DEFAULT_LIQUID, - DEFAULT_WALL, - DEFAULT_FLOOR, - DEFAULT_CEIL - }; - - enum // static entity types - { - NOTUSED = 0, // entity slot not in use in map - LIGHT, // lightsource, attr1 = radius, attr2 = intensity - PLAYERSTART, // attr1 = angle - I_CLIPS, I_AMMO,I_GRENADE, - I_HEALTH, I_ARMOUR, I_QUAD, - MAPMODEL, // attr1 = angle, attr2 = idx - CARROT, // attr1 = tag, attr2 = type - LADDER, - MAXENTTYPES - }; - - struct persistent_entity // map entity - { - short x, y, z; // cube aligned position - short attr1; - uchar type; // type is one of the above - uchar attr2, attr3, attr4; - }; - - struct entity : public persistent_entity - { - bool spawned; // the only dynamic state of a map entity - }; - - #define MAPVERSION 6 // bump if map format changes, see worldio.cpp - - struct header // map file format header - { - char head[4]; // "CUBE" - int version; // any >8bit quantity is a little indian - int headersize; // sizeof(header) - int sfactor; // in bits - int numents; - char maptitle[128]; - uchar texlists[3][256]; - int waterlevel; - int reserved[15]; - }; - - #define SWS(w,x,y,s) (&(w)[(y)*(s)+(x)]) - #define SW(w,x,y) SWS(w,x,y,ssize) - #define S(x,y) SW(world,x,y) // convenient lookup of a lowest mip cube - #define SMALLEST_FACTOR 6 // determines number of mips there can be - #define DEFAULT_FACTOR 8 - #define LARGEST_FACTOR 11 // 10 is already insane - #define SOLID(x) ((x)->type==SOLID) - #define MINBORD 2 // 2 cubes from the edge of the world are always solid - #define OUTBORD(x,y) ((x)=ssize-MINBORD || (y)>=ssize-MINBORD) - - struct vec { float x, y, z; }; - struct block { int x, y, xs, ys; }; - struct mapmodelinfo { int rad, h, zoff, snap; char *name; }; - - enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSULT, GUN_GRENADE, NUMGUNS }; - - struct md3state - { - int anim; - int frm; - int lastTime; - }; - - enum // md3 animations - { - BOTH_DEATH1 = 0, - BOTH_DEAD1, - BOTH_DEATH2, - BOTH_DEAD2, - BOTH_DEATH3, - BOTH_DEAD3, - TORSO_GESTURE, - TORSO_ATTACK, - TORSO_ATTACK2, - TORSO_DROP, - TORSO_RAISE, - TORSO_STAND, - TORSO_STAND2, - LEGS_WALKCR, - LEGS_WALK, - LEGS_RUN, - LEGS_BACK, - LEGS_SWIM, - LEGS_JUMP, - LEGS_LAND, - LEGS_JUMPB, - LEGS_LANDB, - LEGS_IDLE, - LEGS_IDLECR, - LEGS_TURN - }; - - struct dynent // players & monsters - { - vec o, vel; // origin, velocity - float yaw, pitch, roll; // used as vec in one place - float oldpitch; - float maxspeed; // cubes per second, 24 for player - bool outsidemap; // from his eyes - bool inwater; - bool onfloor, jumpnext; - int move, strafe; - bool k_left, k_right, k_up, k_down; // see input code - int timeinair; // used for fake gravity - float radius, eyeheight, aboveeye; // bounding box size - int lastupdate, plag, ping; - int lifesequence; // sequence id for each respawn, used in damage test - int state; // one of CS_* below - int frags; - int health, armour; //armourtype, quadmillis; - int gunselect, gunwait; - int lastaction, lastattackgun, lastmove; - bool attacking; - int ammo[NUMGUNS]; - int mag[NUMGUNS]; - int monsterstate; // one of M_* below, M_NONE means human - int mtype; // see monster.cpp - dynent *enemy; // monster wants to kill this entity - float targetyaw; // monster wants to look in this direction - bool blocked, moving; // used by physics to signal ai - int trigger; // millis at which transition to another monsterstate takes place - vec attacktarget; // delayed attacks - int anger; // how many times already hit by fellow monster - string name, team; - int startheight; - int shots; //keeps track of shots from auto weapons - bool reloading, hasarmour; - int primary; //primary gun - int nextprimary; - md3state animstate[3]; - - int gravity; - bool bounce; - }; - - struct physent : dynent // inheritance: tons of unused stuff, but makes life easier - { - int millis, lifetime; - bool throwed; - bool enabled; - dynent *owner; - }; - - #define SAVEGAMEVERSION 6 // bump if dynent/netprotocol changes or any other savegame/demo data bumped from 5 - - //enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off - enum { M_NONE = 0, M_SEARCH, M_HOME, M_ATTACKING, M_PAIN, M_SLEEP, M_AIMING, M_NADE /* bloody hack*/ }; // monster states - - #define MAXCLIENTS 256 // in a multiplayer game, can be arbitrarily changed - #define MAXTRANS 5000 // max amount of data to swallow in 1 go - #define CUBE_SERVER_PORT 28765 - #define CUBE_SERVINFO_PORT 28766 - #define PROTOCOL_VERSION 121 // bump when protocol changes - - // network messages codes, c2s, c2c, s2c - enum - { - SV_INITS2C = 0, SV_INITC2S, SV_POS, SV_TEXT, SV_SOUND, SV_CDIS, - SV_DIED, SV_DAMAGE, SV_SHOT, SV_FRAGS, - SV_TIMEUP, SV_EDITENT, SV_MAPRELOAD, SV_ITEMACC, - SV_MAPCHANGE, SV_ITEMSPAWN, SV_ITEMPICKUP, SV_DENIED, - SV_PING, SV_PONG, SV_CLIENTPING, SV_GAMEMODE, - SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE, - SV_SENDMAP, SV_RECVMAP, SV_SERVMSG, SV_ITEMLIST, - SV_EXT, - }; - - enum { CS_ALIVE = 0, CS_DEAD, CS_LAGGED, CS_EDITING }; - - // hardcoded sounds, defined in sounds.cfg - enum - { - S_JUMP = 0, S_LAND, - S_KNIFE, - S_PISTOL, S_RPISTOL, - S_SHOTGUN, S_RSHOTGUN, - S_SUBGUN, S_RSUBGUN, - S_SNIPER, S_RSNIPER, - S_ASSULT, S_RASSULT, - S_GRENADE, - S_ITEMAMMO, S_ITEMHEALTH, - S_ITEMARMOUR, S_ITEMPUP, S_ITEMSPAWN, - S_NOAMMO, S_PUPOUT, - S_PAIN1, S_PAIN2, S_PAIN3, S_PAIN4, S_PAIN5, S_PAIN6, - S_DIE1, S_DIE2, S_SUICIDE, - S_FALL1, S_FALL2, - S_FLAUNCH, S_FEXPLODE, - S_SPLASH1, S_SPLASH2, - S_GRUNT1, S_GRUNT2, S_RUMBLE, - S_NULL - }; - - // vertex array format - - struct vertex { float u, v, x, y, z; uchar r, g, b, a; }; - - typedef vector dvector; - typedef vector cvector; - typedef vector ivector; - - // globals ooh naughty - - extern sqr *world, *wmip[]; // map data, the mips are sequential 2D arrays in memory - extern header hdr; // current map header - extern int sfactor, ssize; // ssize = 2^sfactor - extern int cubicsize, mipsize; // cubicsize = ssize^2 - extern dynent *player1; // special client ent that receives input and acts as camera - extern dvector players; // all the other clients (in multiplayer) - extern vectorphysents; - extern bool editmode; - extern vector ents; // map entities - extern vec worldpos; // current target of the crosshair in the world - extern int lastmillis; // last time - extern int curtime; // current frame time - extern int gamemode, nextmode; - extern int xtraverts; - extern bool demoplayback; - - - #define DMF 16.0f - #define DAF 1.0f - #define DVF 100.0f - - #define VIRTW 2400 // virtual screen size for text & HUD - #define VIRTH 1800 - #define FONTH 64 - #define PIXELTAB (VIRTW/12) - - #define PI (3.1415927f) - #define PI2 (2*PI) - - // simplistic vector ops - #define dotprod(u,v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z) - #define vmul(u,f) { (u).x *= (f); (u).y *= (f); (u).z *= (f); } - #define vdiv(u,f) { (u).x /= (f); (u).y /= (f); (u).z /= (f); } - #define vadd(u,v) { (u).x += (v).x; (u).y += (v).y; (u).z += (v).z; }; - #define vsub(u,v) { (u).x -= (v).x; (u).y -= (v).y; (u).z -= (v).z; }; - #define vdist(d,v,e,s) vec v = s; vsub(v,e); float d = (float)sqrt(dotprod(v,v)); - #define vreject(v,u,max) ((v).x>(u).x+(max) || (v).x<(u).x-(max) || (v).y>(u).y+(max) || (v).y<(u).y-(max)) - #define vlinterp(v,f,u,g) { (v).x = (v).x*f+(u).x*g; (v).y = (v).y*f+(u).y*g; (v).z = (v).z*f+(u).z*g; } - - #define sgetstr() { char *t = text; do { *t = getint(p); } while(*t++); } // used by networking - - /* Gamemodes - 0 - tdm - 1 - coop edit - 2 - dm - 3 - survivor - 4 - team survior - 5 - ctf - 6 - pistols - */ - - #define m_noitems (m_arena) - #define m_noitemsrail (false) - #define m_arena (gamemode==3 || gamemode==4) - #define m_tarena (gamemode==4) - #define m_teammode (gamemode==0 || gamemode==4 || gamemode==5) - #define m_ctf (gamemode==5) - #define m_pistol (gamemode==6) - //#define m_sp (gamemode<0) - //#define m_dmsp (gamemode==-1) - //#define m_classicsp (gamemode==-2) - #define isteam(a,b) (m_teammode && strcmp(a, b)==0) - - enum // function signatures for script functions, see command.cpp - { - ARG_1INT, ARG_2INT, ARG_3INT, ARG_4INT, - ARG_NONE, - ARG_1STR, ARG_2STR, ARG_3STR, ARG_5STR, ARG_6STR, - ARG_DOWN, ARG_DWN1, - ARG_1EXP, ARG_2EXP, - ARG_1EST, ARG_2EST, - ARG_VARI - }; - - // nasty macros for registering script functions, abuses globals to avoid excessive infrastructure - #define COMMANDN(name, fun, nargs) static bool __dummy_##fun = addcommand(#name, (void (*)())fun, nargs) - #define COMMAND(name, nargs) COMMANDN(name, name, nargs) - #define VARP(name, min, cur, max) int name = variable(#name, min, cur, max, &name, NULL, true) - #define VAR(name, min, cur, max) int name = variable(#name, min, cur, max, &name, NULL, false) - #define VARF(name, min, cur, max, body) void var_##name(); static int name = variable(#name, min, cur, max, &name, var_##name, false); void var_##name() { body; } - #define VARFP(name, min, cur, max, body) void var_##name(); static int name = variable(#name, min, cur, max, &name, var_##name, true); void var_##name() { body; } - - #define ATOI(s) strtol(s, NULL, 0) // supports hexadecimal numbers - - #ifdef WIN32 - #define WIN32_LEAN_AND_MEAN - #include "windows.h" - #define _WINDOWS - #define ZLIB_DLL - + #define cos(x) cos((float)(x)) + #define sin(x) sin((float)(x)) #else - #include - #endif - - #include - - #include - #include - #include - - #include - #include - - #include - - #include - - #include "protos.h" // external function decls - -#ifdef WIN32 - - #define cos(x) cos((float)(x)) - #define sin(x) sin((float)(x)) - -#endif diff --git a/source/src/rendermd3.cpp b/source/src/rendermd3.cpp index c8dadc33..62807e22 100644 --- a/source/src/rendermd3.cpp +++ b/source/src/rendermd3.cpp @@ -4,7 +4,9 @@ #include "cube.h" const int FIRSTMD3 = 20; -enum { MDL_LOWER = 0, MDL_UPPER, MDL_HEAD }; +enum { MDL_LOWER = 0, MDL_UPPER, MDL_HEAD }; + +enum { MDL_GUN_IDLE = 0, MDL_GUN_ATTACK, MDL_GUN_RELOAD}; #define MD3_DEFAULT_SCALE 0.08f @@ -77,7 +79,8 @@ struct md3model md3tag *tags; int numframes, numtags; bool loaded; - vec scale; + vec scale; + void setanim(int anim); bool link(md3model *link, char *tag); bool load(char *path); void render(); @@ -103,6 +106,12 @@ md3model::~md3model() if(links) free(links); if(tags) delete [] tags; }; + +void md3model::setanim(int anim) +{ + if(animstate->anim != anim) animstate->lastTime = lastmillis; + animstate->anim = anim; +}; bool md3model::link(md3model *link, char *tag) { @@ -173,30 +182,33 @@ bool md3model::load(char *path) loaded = true; return true; }; - -VAR(anim, 0, 0, 100); + +VAR(animdebug, 0, 0, 1); void md3model::render() { if(!loaded || meshes.length() <= 0) return; float t = 0.0f; int nextfrm; - md3state *a = animstate; + md3state *a = animstate; if(a && animations.length() > a->anim) { md3animinfo *info = &animations[a->anim]; if(a->frm < info->start || a->frm > info->end) // we switched to a new animation, jump to the start a->frm = info->start; - t = (float) (lastmillis - a->lastTime) / (1000.0f / info->fps); // t has a value from 0.0f to 1.0f - used for interpolation + t = (float) (lastmillis - a->lastTime) / (1000.0f / (float) info->fps); // t has a value from 0.0f to 1.0f - used for interpolation + //printf("lm:%i lt:%i fps:%i\n", lastmillis, a->lastTime, info->fps); if(t >= 1.0f) // jump to the next keyframe { a->frm++; a->lastTime = lastmillis; - t = 0.0f; + t = 0.0f; + //printf("next keyframe\n"); }; if(a->frm >= info->end) - { + { + //printf("end\n"); if(info->loop) a->frm = info->start; else @@ -207,12 +219,15 @@ void md3model::render() }; } else - { + { + if(animdebug) printf("overflow: curframe %i > maxframe %i\n", a->anim, animations.length()); + a = new md3state(); a->frm = 0; } - a->frm = anim; //fixme - nextfrm = a->frm + 1; + nextfrm = a->frm + 1; + + if(animdebug) printf("interp. from frame %i to %i, %f percent done\n", a->frm, nextfrm, t*100.0f); #define interpolate(p1,p2) ((p1) + t * ((p2) - (p1))) @@ -319,7 +334,7 @@ void md3skin(char *objname, char *skin) // called by the {lower|upper|head}.cfg COMMAND(md3skin, ARG_2STR); -void md3animation(char *first, char *nums, char *loopings, char *fps) /* configurable animations - use hardcoded instead ? */ +void md3animation(char *first, char *nums, char *loopings, char *fps) /* configurable animations */ { md3animinfo &a = tmp_animations.add(); a.start = atoi(first); @@ -330,57 +345,6 @@ void md3animation(char *first, char *nums, char *loopings, char *fps) /* configu COMMAND(md3animation, ARG_5STR); -void loadplayermdl(char *model) -{ - char pl_objects[3][6] = {"lower", "upper", "head"}; - tmp_animations.setsize(0); - sprintf(basedir, "packages/models/players/%s", model); - md3model *mdls[3]; - loopi(3) // load lower,upper and head models - { - sprintf_sd(path)("%s/%s", basedir, pl_objects[i]); - mdls[i] = new md3model(); - models.add(mdls[i]); - sprintf_sd(mdl_path)("%s.md3", path); - sprintf_sd(cfg_path)("%s.cfg", path); - mdls[i]->load(mdl_path); - exec(cfg_path); - }; - mdls[MDL_LOWER]->link(mdls[MDL_UPPER], "tag_torso"); - mdls[MDL_UPPER]->link(mdls[MDL_HEAD], "tag_head"); - sprintf_sd(modelcfg)("%s/animation.cfg", basedir); - exec(modelcfg); // load animations to tmp_animation - int skip_gap = tmp_animations[LEGS_WALKCR].start - tmp_animations[TORSO_GESTURE].start; - for(int i = LEGS_WALKCR; i < tmp_animations.length(); i++) // the upper and lower mdl frames are independent - { - tmp_animations[i].start -= skip_gap; - tmp_animations[i].end -= skip_gap; - }; - loopv(tmp_animations) // assign the loaded animations to the lower,upper or head model - { - md3animinfo &anim = tmp_animations[i]; - if(i <= BOTH_DEAD3) - { - mdls[MDL_UPPER]->animations.add(anim); - mdls[MDL_LOWER]->animations.add(anim); - } - else if(i <= TORSO_STAND2) - mdls[MDL_UPPER]->animations.add(anim); - else if(i <= LEGS_TURN) - mdls[MDL_LOWER]->animations.add(anim); - }; -}; - -COMMAND(loadplayermdl, ARG_1STR); - -enum {MDL_GUN_IDLE, MDL_GUN_RELOAD, NUM_MDL_GUN}; -/*md3animinfo weaponsanim[] = { {0, 8, 8, 25}, - {8, 15, 0, 25}, - {16,25, 0, 25} };*/ - -md3animinfo weaponsanim[] = { {0, 8, 8, 25}, - {8, 25, 0, 25} }; - void loadweapons() { firstweapon = models.length(); @@ -390,10 +354,12 @@ void loadweapons() md3model *mdl = new md3model(); models.add(mdl); sprintf_sd(mdl_path)("%s/tris_high.md3", basedir); - sprintf_sd(cfg_path)("%s/default.skin", basedir); + sprintf_sd(cfg_path)("%s/default.skin", basedir); + sprintf_sd(modelcfg_path) ("%s/animations.cfg", basedir); mdl->load(mdl_path); - exec(cfg_path); - loopj(NUM_MDL_GUN) mdl->animations.add(weaponsanim[j]); + exec(cfg_path); + exec(modelcfg_path); + loopj(tmp_animations.length()) mdl->animations.add(tmp_animations[j]); } }; @@ -438,22 +404,26 @@ void rendermd3gun() light.x = s->r/ll+of; light.y = s->g/ll+of; light.z = s->b/ll+of; - }; + }; - md3model *weapon = models[firstweapon + player1->gunselect]; - int rtime = reloadtime(player1->gunselect); + md3model *weapon = models[firstweapon + player1->gunselect]; + if(!weapon->animstate) + { + weapon->animstate = new md3state(); + weapon->animstate->lastTime = lastmillis; + }; + + int rtime = reloadtime(player1->gunselect); + if(player1->reloading) { - weapon->animstate = new md3state(); - weapon->animstate->anim = MDL_GUN_RELOAD; + weapon->setanim(MDL_GUN_RELOAD); float percent_done = (float)(lastmillis-player1->lastaction)*100.0f/reloadtime(player1->gunselect); if(percent_done >= 100) percent_done = 100; weapon->draw(player1->o.x, player1->o.z, player1->o.y, player1->yaw + 90, player1->pitch-(sin((float)(percent_done*2/100.0f*90.0f)*PI/180.0f)*90), 1.0f, light); } else { - weapon->animstate = new md3state(); - weapon->animstate->anim = MDL_GUN_IDLE; float kick = 0.0f; if(player1->gunselect==player1->lastattackgun) { @@ -461,7 +431,11 @@ void rendermd3gun() if(percent_done > 100) percent_done = 100.0; // f(x) = -sin(x-1.5)^3 kick = -sin(pow((1.5f/100*percent_done)-1.5f,3)); - }; + }; + + if(kick > 0.01f) weapon->setanim(MDL_GUN_ATTACK); + else weapon->setanim(MDL_GUN_IDLE); + vdist(dist, unitv, player1->o, worldpos); vdiv(unitv, dist); float k_rot = kick_rot(player1->gunselect)*kick; @@ -512,32 +486,4 @@ void rendermd3gun() }; }; -void rendermd3player(dynent *d) -{ - /*if(models.length() >= 3) - { - loopi(3) models[i]->animstate = &d->animstate[i]; - - float mz = d->o.z-d->eyeheight+1.55f; - - if(d->state == CS_DEAD) { md3setanim(d, BOTH_DEATH1); } - else if(d->state == CS_EDITING) { md3setanim(d, LEGS_IDLECR); md3setanim(d, TORSO_GESTURE); } - else if(d->state == CS_LAGGED) { md3setanim(d, LEGS_IDLECR); md3setanim(d, TORSO_DROP); } - else - { - if(!d->onfloor && d->timeinair>100) { md3setanim(d, LEGS_JUMP); } - else if((!d->move && !d->strafe)) { md3setanim(d, LEGS_IDLE); } - else { md3setanim(d, LEGS_RUN); } - - if(d->attacking) { md3setanim(d, TORSO_ATTACK2); } - else { md3setanim(d, TORSO_STAND); } - }; - if(d->gunselect >= models.length()) - models[MDL_UPPER]->link(NULL, "tag_weapon"); // no weapon model - else - models[MDL_UPPER]->link(models[d->gunselect], "tag_weapon"); // show current weapon - - models[MDL_LOWER]->draw(d->o.x, mz, d->o.y, d->yaw+90, d->pitch/2, d->radius); - };*/ -};