From 40be4327c87349b5ecd559fb7417005a65d9d881 Mon Sep 17 00:00:00 2001 From: driAn Date: Sun, 8 Jan 2006 15:42:00 +0000 Subject: [PATCH] win stuff fixed git-svn-id: svn://svn.code.sf.net/p/actiongame/code/trunk/ac@108 e016b1c5-ed68-44aa-a2f7-31cac88cffde --- source/src/clientgame.cpp | 1778 +++++++++++++++++++++++++------------ source/src/cube.h | 1051 ++++++++++++++-------- source/src/entities.cpp | 799 +++++++++++------ source/src/physics.cpp | 1448 ++++++++++++++++++++---------- source/src/rendermd3.cpp | 1548 +++++++++++++++++++++----------- 5 files changed, 4418 insertions(+), 2206 deletions(-) diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 1fd68707..ea2e2267 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -1,592 +1,1186 @@ -// clientgame.cpp: core game related stuff - -#include "cube.h" -#define sleep my_sleep -int nextmode = 0; // nextmode becomes gamemode after next map load -VAR(gamemode, 1, 0, 0); - -void mode(int n) { addmsg(1, 2, SV_GAMEMODE, nextmode = n); }; -COMMAND(mode, ARG_1INT); - -bool intermission = false; - -dynent *player1 = newdynent(); // our client -dvector players; // other clients - -vector physents; - -VAR(sensitivity, 0, 10, 1000); -VAR(sensitivityscale, 1, 1, 100); -VAR(invmouse, 0, 0, 1); - -int lastmillis = 0; -int curtime; -string clientmap; - -extern int framesinmap; - -char *getclientmap() { return clientmap; }; - -void resetmovement(dynent *d) -{ - d->k_left = false; - d->k_right = false; - d->k_up = false; - d->k_down = false; - d->jumpnext = false; - d->strafe = 0; - d->move = 0; -}; - -void spawnstate(dynent *d) // reset player state not persistent accross spawns -{ - resetmovement(d); - d->vel.x = d->vel.y = d->vel.z = 0; - d->onfloor = false; - d->timeinair = 0; - d->health = 100; - d->armour = 0; - //d->hasarmour = false; - //d->armourtype = A_BLUE; - //d->quadmillis = 0; - d->gunselect = GUN_PISTOL; - d->mag[6]=10; - d->gunwait = 0; - d->attacking = false; - d->lastaction = 0; - - - radd(d); - - - //loopi(NUMGUNS) if(d->nextprimary!=i) d->ammo[i] = 0; - //d->mag[GUN_KNIFE] = 1; - //equp with default pistol - - //d->mag[GUN_PISTOL] = 8; - //if(d->nextprimary!=GUN_PISTOL) - // d->ammo[GUN_PISTOL] = 16; - //else - // d->ammo[GUN_PISTOL] = 32; - //temp for testing guns - //d->ammo[GUN_SHOTGUN] = 100; - //d->ammo[GUN_SUBGUN] = 400; - //d->ammo[GUN_SNIPER] = 100; - //d->ammo[GUN_ASSULT] = 400; - - //end tmp - - /* - if(m_noitems) - { - d->gunselect = GUN_RIFLE; - d->armour = 0; - if(m_noitemsrail) - { - d->health = 1; - d->ammo[GUN_RIFLE] = 100; - } - else - { - if(gamemode==12) { d->gunselect = GUN_FIST; return; }; // eihruls secret "instafist" mode - d->health = 256; - if(m_tarena) - { - int gun1 = rnd(4)+1; - baseammo(d->gunselect = gun1); - for(;;) - { - int gun2 = rnd(4)+1; - if(gun1!=gun2) { baseammo(gun2); break; }; - }; - } - else if(m_arena) // insta arena - { - d->ammo[GUN_RIFLE] = 100; - } - else // efficiency - { - loopi(4) baseammo(i+1); - d->gunselect = GUN_CG; - }; - d->ammo[GUN_CG] /= 2; - }; - } - else - { - d->ammo[GUN_SG] = 5; - }; - */ -}; - -dynent *newdynent() // create a new blank player or monster -{ - dynent *d = (dynent *)gp()->alloc(sizeof(dynent)); - d->o.x = 0; - d->o.y = 0; - d->o.z = 0; - d->yaw = 270; - d->pitch = 0; - d->roll = 0; - d->maxspeed = 16; //16 max speed, 14 max with armour - d->outsidemap = false; - d->inwater = false; - d->radius = 1.1f; - d->eyeheight = 4.25f; - d->aboveeye = 0.7f; - d->frags = 0; - d->plag = 0; - d->ping = 0; - d->lastupdate = lastmillis; - d->enemy = NULL; - d->monsterstate = 0; - d->name[0] = d->team[0] = 0; - d->blocked = false; - d->lifesequence = 0; - d->state = CS_ALIVE; - d->shots = 0; - d->reloading = false; - d->nextprimary = 1; - d->hasarmour = false; - spawnstate(d); - return d; -}; -void respawnself() -{ - spawnplayer(player1); - showscores(false); -}; - -void respawn() -{ - if(player1->state==CS_DEAD) - { - player1->attacking = false; - if(m_arena) { conoutf("waiting for new round to start..."); return; }; - respawnself(); - }; -}; - -void arenacount(dynent *d, int &alive, int &dead, char *&lastteam, char *&lastname, bool &oneteam) -//void arenacount(dynent *d, int &alive, int &dead, char *&lastteam, bool &oneteam) -{ - if(d->state!=CS_DEAD) - { - alive++; - if(lastteam && strcmp(lastteam, d->team)) oneteam = false; - lastteam = d->team; - lastname = d->name; - } - else - { - dead++; - }; -}; - -int arenarespawnwait = 0; -int arenadetectwait = 0; - -void arenarespawn() -{ - if (!m_arena) return; - - if(arenarespawnwait) - { - if(arenarespawnwait0 && (alive<=1 || (m_teammode && oneteam))) - { - conoutf("arena round is over! next round in 5 seconds..."); - if(alive) - { - if(m_teammode) - conoutf("team %s has won the round", (int)lastteam); - else - conoutf("%s is the survior!", (int)lastname); - } - else conoutf("everyone died!"); - arenarespawnwait = lastmillis+5000; - arenadetectwait = lastmillis+10000; - player1->roll = 0; - }; - }; -}; - - -void checkquad(int time) -{ -/* if(player1->quadmillis && (player1->quadmillis -= time)<0) - { - player1->quadmillis = 0; - playsoundc(S_PUPOUT); - conoutf("quad damage is over"); - }; -*/ -}; - -//generic function to check all kinds of crazy gamemode specific things -void checkgame(int time) -{ - - //allow faster movements when editing -/* if (editmode) - { - player1->maxspeed=22; - return; - } - else //otherwise max speed is 16 and 14 with armour - { - if(player1->armour>50) - player1->maxspeed = 14; - if(player1->armour>0) - player1->maxspeed = 15; - player1->maxspeed = 16; - }; -*/ - - //force to a team if team mode - if (m_teammode) - { - if (strcmp(player1->team,"T") && strcmp(player1->team,"CT")) - { - player1->state = CS_DEAD; - //conoutf("set team to T or CT to joing game"); - if (rnd(2)<1) strcpy_s(player1->team,"T"); - else strcpy_s(player1->team,"CT"); - } - - //add checks here to force-drop flag if switching team with flag - - //add checks for end conditions of ctf - }; - - - //reset zoom if dead - - //etc - //checkquad(time); - - if (player1->state==CS_DEAD) - { - player1->primary = player1->nextprimary; - } -}; - -void zapdynent(dynent *&d) -{ - if(d) gp()->dealloc(d, sizeof(dynent)); - d = NULL; -}; - -extern int democlientnum; - -void otherplayers() -{ - loopv(players) if(players[i]) - { - const int lagtime = lastmillis-players[i]->lastupdate; - if(lagtime>1000 && players[i]->state==CS_ALIVE) - { - players[i]->state = CS_LAGGED; - continue; - }; - if(lagtime && players[i]->state != CS_DEAD && (!demoplayback || i!=democlientnum)) moveplayer(players[i], 2, false); // use physics to extrapolate player position - }; -}; - -extern void explode_nade(physent *i); - -void mphysents() -{ - loopv(physents) if(physents[i]) - { - physent *p = physents[i]; - if(p->throwed) moveplayer(p, 2, false); - if(lastmillis - p->millis >= p->lifetime) - { - explode_nade(physents[i]); - physents.remove(i); - i--; - }; - }; -}; - -int sleepwait = 0; -string sleepcmd; -void sleep(char *msec, char *cmd) { sleepwait = atoi(msec)+lastmillis; strcpy_s(sleepcmd, cmd); }; -COMMAND(sleep, ARG_2STR); - -void updateworld(int millis) // main game update loop -{ - if(lastmillis) - { - curtime = millis - lastmillis; - if(sleepwait && lastmillis>sleepwait) { execute(sleepcmd); sleepwait = 0; }; - physicsframe(); - checkgame(curtime); - //if(m_arena) arenarespawn(); - arenarespawn(); - moveprojectiles((float)curtime); - demoplaybackstep(); - if(!demoplayback) - { - if(getclientnum()>=0) shoot(player1, worldpos); // only shoot when connected to server - gets2c(); // do this first, so we have most accurate information when our player moves - }; - mphysents(); - otherplayers(); - if(!demoplayback) - { - //put game mode extra call here - if(player1->state==CS_DEAD) - { - if(lastmillis-player1->lastaction<2000) - { - player1->move = player1->strafe = 0; - moveplayer(player1, 10, false); - }; - } - else if(!intermission) - { - moveplayer(player1, 20, true); - checkitems(); - }; - c2sinfo(player1); // do this last, to reduce the effective frame lag - }; - }; - lastmillis = millis; -}; - -void entinmap(dynent *d) // brute force but effective way to find a free spawn spot in the map -{ - loopi(100) // try max 100 times - { - float dx = (rnd(21)-10)/10.0f*i; // increasing distance - float dy = (rnd(21)-10)/10.0f*i; - d->o.x += dx; - d->o.y += dy; - if(collide(d, true, 0, 0)) return; - d->o.x -= dx; - d->o.y -= dy; - }; - conoutf("can't find entity spawn spot! (%d, %d)", (int)d->o.x, (int)d->o.y); - // leave ent at original pos, possibly stuck -}; - -int spawncycle = -1; -int fixspawn = 2; - -void spawnplayer(dynent *d) // place at random spawn. also used by monsters! -{ - int r = fixspawn-->0 ? 4 : rnd(10)+1; - loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle+1); - if(spawncycle!=-1) - { - d->o.x = ents[spawncycle].x; - d->o.y = ents[spawncycle].y; - d->o.z = ents[spawncycle].z; - d->yaw = ents[spawncycle].attr1; - d->pitch = 0; - d->roll = 0; - } - else - { - d->o.x = d->o.y = (float)ssize/2; - d->o.z = 4; - }; - entinmap(d); - spawnstate(d); - d->state = CS_ALIVE; -}; - -// movement input code - -#define dir(name,v,d,s,os) void name(bool isdown) { player1->s = isdown; player1->v = isdown ? d : (player1->os ? -(d) : 0); player1->lastmove = lastmillis; }; - -dir(backward, move, -1, k_down, k_up); -dir(forward, move, 1, k_up, k_down); -dir(left, strafe, 1, k_left, k_right); -dir(right, strafe, -1, k_right, k_left); - -void attack(bool on) -{ - if(intermission) return; - if(editmode) editdrag(on); - else if(player1->attacking = on) respawn(); -}; - -void jumpn(bool on) { if(!intermission && (player1->jumpnext = on) ) respawn(); }; - -COMMAND(backward, ARG_DOWN); -COMMAND(forward, ARG_DOWN); -COMMAND(left, ARG_DOWN); -COMMAND(right, ARG_DOWN); -COMMANDN(jump, jumpn, ARG_DOWN); -COMMAND(attack, ARG_DOWN); -COMMAND(showscores, ARG_DOWN); - -void fixplayer1range() -{ - const float MAXPITCH = 90.0f; - if(player1->pitch>MAXPITCH) player1->pitch = MAXPITCH; - if(player1->pitch<-MAXPITCH) player1->pitch = -MAXPITCH; - while(player1->yaw<0.0f) player1->yaw += 360.0f; - while(player1->yaw>=360.0f) player1->yaw -= 360.0f; -}; - -void mousemove(int dx, int dy) -{ - if(player1->state==CS_DEAD || intermission) return; - const float SENSF = 33.0f; // try match quake sens - player1->yaw += (dx/SENSF)*(sensitivity/(float)sensitivityscale); - player1->pitch -= (dy/SENSF)*(sensitivity/(float)sensitivityscale)*(invmouse ? -1 : 1); - fixplayer1range(); -}; - -// damage arriving from the network, monsters, yourself, all ends up here. - -void selfdamage(int damage, int actor, dynent *act) -{ - if(player1->state!=CS_ALIVE || editmode || intermission) return; - damageblend(damage); - demoblend(damage); - int ad = damage*30/100;//int ad = damage*(player1->armourtype+1)*20/100; // let armour absorb when possible - if(ad>player1->armour) ad = player1->armour; - player1->armour -= ad; - damage -= ad; - float droll = damage/0.5f; - player1->roll += player1->roll>0 ? droll : (player1->roll<0 ? -droll : (rnd(2) ? droll : -droll)); // give player a kick depending on amount of damage - if((player1->health -= damage)<=0) - { - if(actor==-2) - { - conoutf("you got killed by %s!", (int)&act->name); - } - else if(actor==-1) - { - actor = getclientnum(); - conoutf("you suicided!"); - addmsg(1, 2, SV_FRAGS, --player1->frags); - } - else - { - dynent *a = getclient(actor); - if(a) - { - if(isteam(a->team, player1->team)) - { - conoutf("you got fragged by a teammate (%s)", (int)a->name); - } - else - { - conoutf("you got fragged by %s", (int)a->name); - }; - }; - }; - showscores(true); - addmsg(1, 2, SV_DIED, actor); - player1->lifesequence++; - player1->attacking = false; - player1->state = CS_DEAD; - player1->oldpitch = player1->pitch; - player1->pitch = 0; - player1->roll = 60; - playsound(S_DIE1+rnd(2)); - spawnstate(player1); - player1->lastaction = lastmillis; - } - else - { - playsound(S_PAIN6); - }; -}; - -void timeupdate(int timeremain) -{ - if(!timeremain) - { - intermission = true; - player1->attacking = false; - conoutf("intermission:"); - conoutf("game has ended!"); - showscores(true); - } - else - { - conoutf("time remaining: %d minutes", timeremain); - }; -}; - -dynent *getclient(int cn) // ensure valid entity -{ - if(cn<0 || cn>=MAXCLIENTS) - { - neterr("clientnum"); - return NULL; - }; - while(cn>=players.length()) players.add(NULL); - return players[cn] ? players[cn] : (players[cn] = newdynent()); -}; - -void addc() { strcpy(getclient(1)->team, "CT"); getclient(1)->o = player1->o; strcpy(getclient(1)->name, "sucker"); } COMMAND(addc, ARG_NONE); // FIXME - -void initclient() -{ - clientmap[0] = 0; - initclientnet(); -}; - -void startmap(char *name) // called just after a map load -{ - //if(netmapstart()) { gamemode = 0;}; //needs fixed to switch modes? - netmapstart(); //should work - sleepwait = 0; - //put call to clear/restart game mode extras here - projreset(); - shotlinereset(); - spawncycle = -1; - spawnplayer(player1); - player1->frags = 0; - loopv(players) if(players[i]) players[i]->frags = 0; - resetspawns(); - strcpy_s(clientmap, name); - if(editmode) toggleedit(); - setvar("gamespeed", 100); - setvar("fog", 180); - setvar("fogcolour", 0x8099B3); - showscores(false); - intermission = false; - framesinmap = 0; - conoutf("game mode is %s", (int)modestr(gamemode)); -}; - -COMMANDN(map, changemap, ARG_1STR); - -void suicide() -{ - if (multiplayer()) conoutf("there are others willing to kill you..."); - else conoutf("this is not the answer"); - selfdamage(1000, -1, player1); - //if(d==player1) selfdamage(dm, -1, d); - //else { addmsg(1, 4, SV_DAMAGE, d, dm, d->lifesequence); playsound(S_FALL1+rnd(5), &d->o); }; - //particle_splash(3, 1000, 1000, player1->o); //edit out? - demodamage(1000, player1->o); - playsound(S_SUICIDE, &player1->o); - -}; - -COMMAND(suicide, ARG_NONE); +// clientgame.cpp: core game related stuff + + + +#include "cube.h" + +#define sleep my_sleep + +int nextmode = 0; // nextmode becomes gamemode after next map load + +VAR(gamemode, 1, 0, 0); + + + +void mode(int n) { addmsg(1, 2, SV_GAMEMODE, nextmode = n); }; + +COMMAND(mode, ARG_1INT); + + + +bool intermission = false; + + + +dynent *player1 = newdynent(); // our client + +dvector players; // other clients + + + +vector physents; + + + +VAR(sensitivity, 0, 10, 1000); + +VAR(sensitivityscale, 1, 1, 100); + +VAR(invmouse, 0, 0, 1); + + + +int lastmillis = 0; + +int curtime; + +string clientmap; + + + +extern int framesinmap; + + + +char *getclientmap() { return clientmap; }; + + + +void resetmovement(dynent *d) + +{ + + d->k_left = false; + + d->k_right = false; + + d->k_up = false; + + d->k_down = false; + + d->jumpnext = false; + + d->strafe = 0; + + d->move = 0; + +}; + + + +void spawnstate(dynent *d) // reset player state not persistent accross spawns + +{ + + resetmovement(d); + + d->vel.x = d->vel.y = d->vel.z = 0; + + d->onfloor = false; + + d->timeinair = 0; + + d->health = 100; + + d->armour = 0; + + //d->hasarmour = false; + + //d->armourtype = A_BLUE; + + //d->quadmillis = 0; + + d->gunselect = GUN_PISTOL; + + d->mag[6]=10; + + d->gunwait = 0; + + d->attacking = false; + + d->lastaction = 0; + + + + + + radd(d); + + + + + + //loopi(NUMGUNS) if(d->nextprimary!=i) d->ammo[i] = 0; + + //d->mag[GUN_KNIFE] = 1; + + //equp with default pistol + + + + //d->mag[GUN_PISTOL] = 8; + + //if(d->nextprimary!=GUN_PISTOL) + + // d->ammo[GUN_PISTOL] = 16; + + //else + + // d->ammo[GUN_PISTOL] = 32; + + //temp for testing guns + + //d->ammo[GUN_SHOTGUN] = 100; + + //d->ammo[GUN_SUBGUN] = 400; + + //d->ammo[GUN_SNIPER] = 100; + + //d->ammo[GUN_ASSULT] = 400; + + + + //end tmp + + + + /* + + if(m_noitems) + + { + + d->gunselect = GUN_RIFLE; + + d->armour = 0; + + if(m_noitemsrail) + + { + + d->health = 1; + + d->ammo[GUN_RIFLE] = 100; + + } + + else + + { + + if(gamemode==12) { d->gunselect = GUN_FIST; return; }; // eihruls secret "instafist" mode + + d->health = 256; + + if(m_tarena) + + { + + int gun1 = rnd(4)+1; + + baseammo(d->gunselect = gun1); + + for(;;) + + { + + int gun2 = rnd(4)+1; + + if(gun1!=gun2) { baseammo(gun2); break; }; + + }; + + } + + else if(m_arena) // insta arena + + { + + d->ammo[GUN_RIFLE] = 100; + + } + + else // efficiency + + { + + loopi(4) baseammo(i+1); + + d->gunselect = GUN_CG; + + }; + + d->ammo[GUN_CG] /= 2; + + }; + + } + + else + + { + + d->ammo[GUN_SG] = 5; + + }; + + */ + +}; + + + +dynent *newdynent() // create a new blank player or monster + +{ + + dynent *d = (dynent *)gp()->alloc(sizeof(dynent)); + + d->o.x = 0; + + d->o.y = 0; + + d->o.z = 0; + + d->yaw = 270; + + d->pitch = 0; + + d->roll = 0; + + d->maxspeed = 16; //16 max speed, 14 max with armour + + d->outsidemap = false; + + d->inwater = false; + + d->radius = 1.1f; + + d->eyeheight = 4.25f; + + d->aboveeye = 0.7f; + + d->frags = 0; + + d->plag = 0; + + d->ping = 0; + + d->lastupdate = lastmillis; + + d->enemy = NULL; + + d->monsterstate = 0; + + d->name[0] = d->team[0] = 0; + + d->blocked = false; + + d->lifesequence = 0; + + d->state = CS_ALIVE; + + d->shots = 0; + + d->reloading = false; + + d->nextprimary = 1; + + d->hasarmour = false; + + d->bounce = false; + + spawnstate(d); + + return d; + +}; + +void respawnself() + +{ + + spawnplayer(player1); + + showscores(false); + +}; + + + +void respawn() + +{ + + if(player1->state==CS_DEAD) + + { + + player1->attacking = false; + + if(m_arena) { conoutf("waiting for new round to start..."); return; }; + + respawnself(); + + }; + +}; + + + +void arenacount(dynent *d, int &alive, int &dead, char *&lastteam, char *&lastname, bool &oneteam) + +//void arenacount(dynent *d, int &alive, int &dead, char *&lastteam, bool &oneteam) + +{ + + if(d->state!=CS_DEAD) + + { + + alive++; + + if(lastteam && strcmp(lastteam, d->team)) oneteam = false; + + lastteam = d->team; + + lastname = d->name; + + } + + else + + { + + dead++; + + }; + +}; + + + +int arenarespawnwait = 0; + +int arenadetectwait = 0; + + + +void arenarespawn() + +{ + + if (!m_arena) return; + + + + if(arenarespawnwait) + + { + + if(arenarespawnwait0 && (alive<=1 || (m_teammode && oneteam))) + + { + + conoutf("arena round is over! next round in 5 seconds..."); + + if(alive) + + { + + if(m_teammode) + + conoutf("team %s has won the round", (int)lastteam); + + else + + conoutf("%s is the survior!", (int)lastname); + + } + + else conoutf("everyone died!"); + + arenarespawnwait = lastmillis+5000; + + arenadetectwait = lastmillis+10000; + + player1->roll = 0; + + }; + + }; + +}; + + + + + +void checkquad(int time) + +{ + +/* if(player1->quadmillis && (player1->quadmillis -= time)<0) + + { + + player1->quadmillis = 0; + + playsoundc(S_PUPOUT); + + conoutf("quad damage is over"); + + }; + +*/ + +}; + + + +//generic function to check all kinds of crazy gamemode specific things + +void checkgame(int time) + +{ + + + + //allow faster movements when editing + +/* if (editmode) + + { + + player1->maxspeed=22; + + return; + + } + + else //otherwise max speed is 16 and 14 with armour + + { + + if(player1->armour>50) + + player1->maxspeed = 14; + + if(player1->armour>0) + + player1->maxspeed = 15; + + player1->maxspeed = 16; + + }; + +*/ + + + + //force to a team if team mode + + if (m_teammode) + + { + + if (strcmp(player1->team,"T") && strcmp(player1->team,"CT")) + + { + + player1->state = CS_DEAD; + + //conoutf("set team to T or CT to joing game"); + + if (rnd(2)<1) strcpy_s(player1->team,"T"); + + else strcpy_s(player1->team,"CT"); + + } + + + + //add checks here to force-drop flag if switching team with flag + + + + //add checks for end conditions of ctf + + }; + + + + + + //reset zoom if dead + + + + //etc + + //checkquad(time); + + + + if (player1->state==CS_DEAD) + + { + + player1->primary = player1->nextprimary; + + } + +}; + + + +void zapdynent(dynent *&d) + +{ + + if(d) gp()->dealloc(d, sizeof(dynent)); + + d = NULL; + +}; + + + +extern int democlientnum; + + + +void otherplayers() + +{ + + loopv(players) if(players[i]) + + { + + const int lagtime = lastmillis-players[i]->lastupdate; + + if(lagtime>1000 && players[i]->state==CS_ALIVE) + + { + + players[i]->state = CS_LAGGED; + + continue; + + }; + + if(lagtime && players[i]->state != CS_DEAD && (!demoplayback || i!=democlientnum)) moveplayer(players[i], 2, false); // use physics to extrapolate player position + + }; + +}; + + + +extern void explode_nade(physent *i); + + + +void mphysents() + +{ + + loopv(physents) if(physents[i]) + + { + + physent *p = physents[i]; + + if(p->throwed) moveplayer(p, 2, false); + + if(lastmillis - p->millis >= p->lifetime) + + { + + explode_nade(physents[i]); + + physents.remove(i); + + i--; + + }; + + }; + +}; + + + +int sleepwait = 0; + +string sleepcmd; + +void sleep(char *msec, char *cmd) { sleepwait = atoi(msec)+lastmillis; strcpy_s(sleepcmd, cmd); }; + +COMMAND(sleep, ARG_2STR); + + + +void updateworld(int millis) // main game update loop + +{ + + if(lastmillis) + + { + + curtime = millis - lastmillis; + + if(sleepwait && lastmillis>sleepwait) { execute(sleepcmd); sleepwait = 0; }; + + physicsframe(); + + checkgame(curtime); + + //if(m_arena) arenarespawn(); + + arenarespawn(); + + moveprojectiles((float)curtime); + + demoplaybackstep(); + + if(!demoplayback) + + { + + if(getclientnum()>=0) shoot(player1, worldpos); // only shoot when connected to server + + gets2c(); // do this first, so we have most accurate information when our player moves + + }; + + mphysents(); + + otherplayers(); + + if(!demoplayback) + + { + + //put game mode extra call here + + if(player1->state==CS_DEAD) + + { + + if(lastmillis-player1->lastaction<2000) + + { + + player1->move = player1->strafe = 0; + + moveplayer(player1, 10, false); + + }; + + } + + else if(!intermission) + + { + + moveplayer(player1, 20, true); + + checkitems(); + + }; + + c2sinfo(player1); // do this last, to reduce the effective frame lag + + }; + + }; + + lastmillis = millis; + +}; + + + +void entinmap(dynent *d) // brute force but effective way to find a free spawn spot in the map + +{ + + loopi(100) // try max 100 times + + { + + float dx = (rnd(21)-10)/10.0f*i; // increasing distance + + float dy = (rnd(21)-10)/10.0f*i; + + d->o.x += dx; + + d->o.y += dy; + + if(collide(d, true, 0, 0)) return; + + d->o.x -= dx; + + d->o.y -= dy; + + }; + + conoutf("can't find entity spawn spot! (%d, %d)", (int)d->o.x, (int)d->o.y); + + // leave ent at original pos, possibly stuck + +}; + + + +int spawncycle = -1; + +int fixspawn = 2; + + + +void spawnplayer(dynent *d) // place at random spawn. also used by monsters! + +{ + + int r = fixspawn-->0 ? 4 : rnd(10)+1; + + loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle+1); + + if(spawncycle!=-1) + + { + + d->o.x = ents[spawncycle].x; + + d->o.y = ents[spawncycle].y; + + d->o.z = ents[spawncycle].z; + + d->yaw = ents[spawncycle].attr1; + + d->pitch = 0; + + d->roll = 0; + + } + + else + + { + + d->o.x = d->o.y = (float)ssize/2; + + d->o.z = 4; + + }; + + entinmap(d); + + spawnstate(d); + + d->state = CS_ALIVE; + +}; + + + +// movement input code + + + +#define dir(name,v,d,s,os) void name(bool isdown) { player1->s = isdown; player1->v = isdown ? d : (player1->os ? -(d) : 0); player1->lastmove = lastmillis; }; + + + +dir(backward, move, -1, k_down, k_up); + +dir(forward, move, 1, k_up, k_down); + +dir(left, strafe, 1, k_left, k_right); + +dir(right, strafe, -1, k_right, k_left); + + + +void attack(bool on) + +{ + + if(intermission) return; + + if(editmode) editdrag(on); + + else if(player1->attacking = on) respawn(); + +}; + + + +void jumpn(bool on) { if(!intermission && (player1->jumpnext = on) ) respawn(); }; + + + +COMMAND(backward, ARG_DOWN); + +COMMAND(forward, ARG_DOWN); + +COMMAND(left, ARG_DOWN); + +COMMAND(right, ARG_DOWN); + +COMMANDN(jump, jumpn, ARG_DOWN); + +COMMAND(attack, ARG_DOWN); + +COMMAND(showscores, ARG_DOWN); + + + +void fixplayer1range() + +{ + + const float MAXPITCH = 90.0f; + + if(player1->pitch>MAXPITCH) player1->pitch = MAXPITCH; + + if(player1->pitch<-MAXPITCH) player1->pitch = -MAXPITCH; + + while(player1->yaw<0.0f) player1->yaw += 360.0f; + + while(player1->yaw>=360.0f) player1->yaw -= 360.0f; + +}; + + + +void mousemove(int dx, int dy) + +{ + + if(player1->state==CS_DEAD || intermission) return; + + const float SENSF = 33.0f; // try match quake sens + + player1->yaw += (dx/SENSF)*(sensitivity/(float)sensitivityscale); + + player1->pitch -= (dy/SENSF)*(sensitivity/(float)sensitivityscale)*(invmouse ? -1 : 1); + + fixplayer1range(); + +}; + + + +// damage arriving from the network, monsters, yourself, all ends up here. + + + +void selfdamage(int damage, int actor, dynent *act) + +{ + + if(player1->state!=CS_ALIVE || editmode || intermission) return; + + damageblend(damage); + + demoblend(damage); + + int ad = damage*30/100;//int ad = damage*(player1->armourtype+1)*20/100; // let armour absorb when possible + + if(ad>player1->armour) ad = player1->armour; + + player1->armour -= ad; + + damage -= ad; + + float droll = damage/0.5f; + + player1->roll += player1->roll>0 ? droll : (player1->roll<0 ? -droll : (rnd(2) ? droll : -droll)); // give player a kick depending on amount of damage + + if((player1->health -= damage)<=0) + + { + + if(actor==-2) + + { + + conoutf("you got killed by %s!", (int)&act->name); + + } + + else if(actor==-1) + + { + + actor = getclientnum(); + + conoutf("you suicided!"); + + addmsg(1, 2, SV_FRAGS, --player1->frags); + + } + + else + + { + + dynent *a = getclient(actor); + + if(a) + + { + + if(isteam(a->team, player1->team)) + + { + + conoutf("you got fragged by a teammate (%s)", (int)a->name); + + } + + else + + { + + conoutf("you got fragged by %s", (int)a->name); + + }; + + }; + + }; + + showscores(true); + + addmsg(1, 2, SV_DIED, actor); + + player1->lifesequence++; + + player1->attacking = false; + + player1->state = CS_DEAD; + + player1->oldpitch = player1->pitch; + + player1->pitch = 0; + + player1->roll = 60; + + playsound(S_DIE1+rnd(2)); + + spawnstate(player1); + + player1->lastaction = lastmillis; + + } + + else + + { + + playsound(S_PAIN6); + + }; + +}; + + + +void timeupdate(int timeremain) + +{ + + if(!timeremain) + + { + + intermission = true; + + player1->attacking = false; + + conoutf("intermission:"); + + conoutf("game has ended!"); + + showscores(true); + + } + + else + + { + + conoutf("time remaining: %d minutes", timeremain); + + }; + +}; + + + +dynent *getclient(int cn) // ensure valid entity + +{ + + if(cn<0 || cn>=MAXCLIENTS) + + { + + neterr("clientnum"); + + return NULL; + + }; + + while(cn>=players.length()) players.add(NULL); + + return players[cn] ? players[cn] : (players[cn] = newdynent()); + +}; + + + +void addc() { strcpy(getclient(1)->team, "CT"); getclient(1)->o = player1->o; strcpy(getclient(1)->name, "sucker"); } COMMAND(addc, ARG_NONE); // FIXME + + + +void initclient() + +{ + + clientmap[0] = 0; + + initclientnet(); + +}; + + + +void startmap(char *name) // called just after a map load + +{ + + //if(netmapstart()) { gamemode = 0;}; //needs fixed to switch modes? + + netmapstart(); //should work + + sleepwait = 0; + + //put call to clear/restart game mode extras here + + projreset(); + + shotlinereset(); + + spawncycle = -1; + + spawnplayer(player1); + + player1->frags = 0; + + loopv(players) if(players[i]) players[i]->frags = 0; + + resetspawns(); + + strcpy_s(clientmap, name); + + if(editmode) toggleedit(); + + setvar("gamespeed", 100); + + setvar("fog", 180); + + setvar("fogcolour", 0x8099B3); + + showscores(false); + + intermission = false; + + framesinmap = 0; + + conoutf("game mode is %s", (int)modestr(gamemode)); + +}; + + + +COMMANDN(map, changemap, ARG_1STR); + + + +void suicide() + +{ + + if (multiplayer()) conoutf("there are others willing to kill you..."); + + else conoutf("this is not the answer"); + + selfdamage(1000, -1, player1); + + //if(d==player1) selfdamage(dm, -1, d); + + //else { addmsg(1, 4, SV_DAMAGE, d, dm, d->lifesequence); playsound(S_FALL1+rnd(5), &d->o); }; + + //particle_splash(3, 1000, 1000, player1->o); //edit out? + + demodamage(1000, player1->o); + + playsound(S_SUICIDE, &player1->o); + + + +}; + + + +COMMAND(suicide, ARG_NONE); + diff --git a/source/src/cube.h b/source/src/cube.h index edd269e6..1d4ed058 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -1,350 +1,701 @@ -/// 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 - +/// 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 + +#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/entities.cpp b/source/src/entities.cpp index f7e157cc..dd3817f7 100644 --- a/source/src/entities.cpp +++ b/source/src/entities.cpp @@ -1,265 +1,534 @@ -// entities.cpp: map entity related functions (pickup etc.) - -#include "cube.h" - -vector ents; - -char *entmdlnames[] = -{ -//FIXME : fix the "pickups" infront - "pickups/pistolclips", "pickups/ammobox", "ammobox" /*grenade*/, "pickups/health", "pickups/kevlar", "pickups/duals" /*dual pistols*/, -}; - -int triggertime = 0; - -void renderent(entity &e, char *mdlname, float z, float yaw, int frame = 0, int numf = 1, int basetime = 0, float speed = 10.0f) -{ - rendermodel(mdlname, frame, numf, 0, 1.1f, e.x, z+S(e.x, e.y)->floor, e.y, yaw, 0, false, 1.0f, speed, 0, basetime); -}; - -void renderentities() -{ - if(lastmillis>triggertime+1000) triggertime = 0; - loopv(ents) - { - entity &e = ents[i]; - if(e.type==MAPMODEL) - { - mapmodelinfo &mmi = getmminfo(e.attr2); - if(!&mmi) continue; - rendermodel(mmi.name, 0, 1, e.attr4, (float)mmi.rad, e.x, (float)S(e.x, e.y)->floor+mmi.zoff+e.attr3, e.y, (float)((e.attr1+7)-(e.attr1+7)%15), 0, false, 1.0f, 10.0f, mmi.snap); - } - else - { - if(OUTBORD(e.x, e.y)) continue; - if(e.type!=CARROT) - { - if(!e.spawned) continue; - if(e.typeI_QUAD) continue; - renderent(e, entmdlnames[e.type-I_CLIPS], (float)(1+sin(lastmillis/100.0+e.x+e.y)/20), lastmillis/10.0f); - } - else switch(e.attr2) - { - case 1: - case 3: - continue; - - case 2: - case 0: - if(!e.spawned) continue; - renderent(e, "carrot", (float)(1+sin(lastmillis/100.0+e.x+e.y)/20), lastmillis/(e.attr2 ? 1.0f : 10.0f)); - break; - - case 4: renderent(e, "switch2", 3, (float)e.attr3*90, (!e.spawned && !triggertime) ? 1 : 0, (e.spawned || !triggertime) ? 1 : 2, triggertime, 1050.0f); break; - case 5: renderent(e, "switch1", -0.15f, (float)e.attr3*90, (!e.spawned && !triggertime) ? 30 : 0, (e.spawned || !triggertime) ? 1 : 30, triggertime, 35.0f); break; - }; - }; - }; -}; - -struct itemstat { int add, start, max, sound; } itemstats[] = -{ - 16, 24, 36, S_ITEMAMMO, //pistol - 7, 7, 14, S_ITEMAMMO, //shotgun - 30, 60, 90, S_ITEMAMMO, //subgun - 5, 10, 15, S_ITEMAMMO, //sniper - 20, 20, 40, S_ITEMAMMO, //assult - 1, 0, 2, S_ITEMAMMO, //grenade - 33, 100, 100, S_ITEMHEALTH, //health - 50, 100, 100, S_ITEMARMOUR, //armour - 20000, 0, 30000, S_ITEMPUP, //powerup -}; - -void baseammo(int gun) { player1->ammo[gun] = itemstats[gun-1].add*2; }; - -// these two functions are called when the server acknowledges that you really -// picked up the item (in multiplayer someone may grab it before you). - -/* -void radditem(int i, int &v) -{ - itemstat &is = itemstats[ents[i].type-I_CLIPS]; - ents[i].spawned = false; - v += is.add; - if(v>is.max) v = is.max; - playsoundc(is.sound); -}; -*/ - -void radditem(int i, int &v, int t) -{ - itemstat &is = itemstats[t-1]; - ents[i].spawned = false; - v += is.add; - if(v>is.max) v = is.max; - playsoundc(is.sound); -}; - -void realpickup(int n, dynent *d) -{ - switch(ents[n].type) - { - //case I_PISTOL: radditem(n, d->ammo[1]); break; - //case I_SHOTGUN: radditem(n, d->ammo[2]); break; - //case I_SUBGUN: radditem(n, d->ammo[3]); break; - //case I_SNIPER: radditem(n, d->ammo[4]); break; - //case I_ASSULT: radditem(n, d->ammo[5]); break; - case I_CLIPS: radditem(n, d->ammo[1], 1); break; - case I_AMMO: radditem(n, d->ammo[d->primary], d->primary); break; - case I_GRENADE: radditem(n, d->ammo[6], 6); break; - case I_HEALTH: radditem(n, d->health, 7); break; - - case I_ARMOUR: - radditem(n, d->armour, 8); - //d->hasarmour = true; - break; - - case I_QUAD: - //radditem(n, d->quadmillis, 9); - conoutf("a lesser man would use a single pistol"); - break; - }; -}; - -// these functions are called when the client touches the item - -/* -void additem(int i, int &v, int spawnsec) -{ - if(v4 ? 2 : 3); // spawn times are dependent on number of players - int ammo = np*2; - switch(ents[n].type) - { - //case I_PISTOL: additem(n, d->ammo[1], ammo); break; - //case I_SHOTGUN: additem(n, d->ammo[2], ammo); break; - //case I_SUBGUN: additem(n, d->ammo[3], ammo); break; - //case I_SNIPER: additem(n, d->ammo[4], ammo); break; - //case I_ASSULT: additem(n, d->ammo[5], ammo); break; - case I_CLIPS: additem(n, d->ammo[1], ammo, 1); break; - case I_AMMO: additem(n, d->ammo[d->primary], ammo, d->primary); break; - case I_GRENADE: additem(n, d->ammo[6], ammo, 6); break; - case I_HEALTH: additem(n, d->health, np*5, 7); break; - - case I_ARMOUR: - additem(n, d->armour, 20, 8); - break; - - case I_QUAD: - //additem(n, d->quadmillis, 60, 9); - break; - - case CARROT: - ents[n].spawned = false; - triggertime = lastmillis; - trigger(ents[n].attr1, ents[n].attr2, false); // needs to go over server for multiplayer - break; - - }; -}; - -void checkitems() -{ - if(editmode) return; - loopv(ents) - { - entity &e = ents[i]; - if(e.type==NOTUSED) continue; - if(!ents[i].spawned) continue; - if(OUTBORD(e.x, e.y)) continue; - vec v = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; - vdist(dist, t, player1->o, v); - if(dist<2.5) pickup(i, player1); - }; -}; - -void putitems(uchar *&p) // puts items in network stream and also spawns them locally -{ - loopv(ents) if((ents[i].type>=I_CLIPS && ents[i].type<=I_QUAD) || ents[i].type==CARROT) - { - putint(p, i); - ents[i].spawned = true; - }; -}; - -void resetspawns() { loopv(ents) ents[i].spawned = false; }; -void setspawn(int i, bool on) { if(inextprimary!=i) d->ammo[i] = 0; - d->mag[GUN_KNIFE] = 1; - - d->mag[GUN_PISTOL] = 8; - d->ammo[GUN_PISTOL] = 24; - - if (m_pistol || d->primary==GUN_PISTOL) // || pistol only mode! - { - d->primary = GUN_PISTOL; - d->ammo[d->primary] = 72; - } - else if (d->primary>GUN_PISTOL && d->primaryammo[d->primary] = itemstats[d->primary-1].max; - else - d->ammo[d->primary] = itemstats[d->primary-1].start; - - d->mag[d->primary] = itemstats[d->primary-1].add; - } - -/* - if (d->primary==GUN_GRENADE) - { - //conoutf("you don't have to worry about blowing your hand off just yet..."); - } -*/ - if (d->hasarmour) - { - if(gamemode==m_arena) - d->armour = 100; - } - - d->gunselect = d->primary; //draw main weapon -}; - -void item(int num) -{ - if (num>0 && num<7) player1->nextprimary = num; - - //if (num==6) conoutf("sorry, no nades yet"); - - if (num==7) { player1->hasarmour=!player1->hasarmour; /*toggle armour*/ }; - -}; - -void primary(int num) -{ - if(num>0 && num<7) player1->primary = num; - radd(player1); -} - - -COMMAND(item,ARG_1INT); -COMMAND(primary,ARG_1INT); +// entities.cpp: map entity related functions (pickup etc.) + + + +#include "cube.h" + + + +vector ents; + + + +char *entmdlnames[] = + +{ + +//FIXME : fix the "pickups" infront + + "pickups/pistolclips", "pickups/ammobox", "ammobox" /*grenade*/, "pickups/health", "pickups/kevlar", "pickups/duals" /*dual pistols*/, + +}; + + + +int triggertime = 0; + + + +void renderent(entity &e, char *mdlname, float z, float yaw, int frame = 0, int numf = 1, int basetime = 0, float speed = 10.0f) + +{ + + rendermodel(mdlname, frame, numf, 0, 1.1f, e.x, z+S(e.x, e.y)->floor, e.y, yaw, 0, false, 1.0f, speed, 0, basetime); + +}; + + + +void renderentities() + +{ + + if(lastmillis>triggertime+1000) triggertime = 0; + + loopv(ents) + + { + + entity &e = ents[i]; + + if(e.type==MAPMODEL) + + { + + mapmodelinfo &mmi = getmminfo(e.attr2); + + if(!&mmi) continue; + + rendermodel(mmi.name, 0, 1, e.attr4, (float)mmi.rad, e.x, (float)S(e.x, e.y)->floor+mmi.zoff+e.attr3, e.y, (float)((e.attr1+7)-(e.attr1+7)%15), 0, false, 1.0f, 10.0f, mmi.snap); + + } + + else + + { + + if(OUTBORD(e.x, e.y)) continue; + + if(e.type!=CARROT) + + { + + if(!e.spawned) continue; + + if(e.typeI_QUAD) continue; + + renderent(e, entmdlnames[e.type-I_CLIPS], (float)(1+sin(lastmillis/100.0+e.x+e.y)/20), lastmillis/10.0f); + + } + + else switch(e.attr2) + + { + + case 1: + + case 3: + + continue; + + + + case 2: + + case 0: + + if(!e.spawned) continue; + + renderent(e, "carrot", (float)(1+sin(lastmillis/100.0+e.x+e.y)/20), lastmillis/(e.attr2 ? 1.0f : 10.0f)); + + break; + + + + case 4: renderent(e, "switch2", 3, (float)e.attr3*90, (!e.spawned && !triggertime) ? 1 : 0, (e.spawned || !triggertime) ? 1 : 2, triggertime, 1050.0f); break; + + case 5: renderent(e, "switch1", -0.15f, (float)e.attr3*90, (!e.spawned && !triggertime) ? 30 : 0, (e.spawned || !triggertime) ? 1 : 30, triggertime, 35.0f); break; + + }; + + }; + + }; + +}; + + + +struct itemstat { int add, start, max, sound; } itemstats[] = + +{ + + 16, 24, 36, S_ITEMAMMO, //pistol + + 7, 7, 14, S_ITEMAMMO, //shotgun + + 30, 60, 90, S_ITEMAMMO, //subgun + + 5, 10, 15, S_ITEMAMMO, //sniper + + 20, 20, 40, S_ITEMAMMO, //assult + + 1, 0, 2, S_ITEMAMMO, //grenade + + 33, 100, 100, S_ITEMHEALTH, //health + + 50, 100, 100, S_ITEMARMOUR, //armour + + 20000, 0, 30000, S_ITEMPUP, //powerup + +}; + + + +void baseammo(int gun) { player1->ammo[gun] = itemstats[gun-1].add*2; }; + + + +// these two functions are called when the server acknowledges that you really + +// picked up the item (in multiplayer someone may grab it before you). + + + +/* + +void radditem(int i, int &v) + +{ + + itemstat &is = itemstats[ents[i].type-I_CLIPS]; + + ents[i].spawned = false; + + v += is.add; + + if(v>is.max) v = is.max; + + playsoundc(is.sound); + +}; + +*/ + + + +void radditem(int i, int &v, int t) + +{ + + itemstat &is = itemstats[t-1]; + + ents[i].spawned = false; + + v += is.add; + + if(v>is.max) v = is.max; + + playsoundc(is.sound); + +}; + + + +void realpickup(int n, dynent *d) + +{ + + switch(ents[n].type) + + { + + //case I_PISTOL: radditem(n, d->ammo[1]); break; + + //case I_SHOTGUN: radditem(n, d->ammo[2]); break; + + //case I_SUBGUN: radditem(n, d->ammo[3]); break; + + //case I_SNIPER: radditem(n, d->ammo[4]); break; + + //case I_ASSULT: radditem(n, d->ammo[5]); break; + + case I_CLIPS: radditem(n, d->ammo[1], 1); break; + + case I_AMMO: radditem(n, d->ammo[d->primary], d->primary); break; + + case I_GRENADE: radditem(n, d->ammo[6], 6); break; + + case I_HEALTH: radditem(n, d->health, 7); break; + + + + case I_ARMOUR: + + radditem(n, d->armour, 8); + + //d->hasarmour = true; + + break; + + + + case I_QUAD: + + //radditem(n, d->quadmillis, 9); + + conoutf("a lesser man would use a single pistol"); + + break; + + }; + +}; + + + +// these functions are called when the client touches the item + + + +/* + +void additem(int i, int &v, int spawnsec) + +{ + + if(v4 ? 2 : 3); // spawn times are dependent on number of players + + int ammo = np*2; + + switch(ents[n].type) + + { + + //case I_PISTOL: additem(n, d->ammo[1], ammo); break; + + //case I_SHOTGUN: additem(n, d->ammo[2], ammo); break; + + //case I_SUBGUN: additem(n, d->ammo[3], ammo); break; + + //case I_SNIPER: additem(n, d->ammo[4], ammo); break; + + //case I_ASSULT: additem(n, d->ammo[5], ammo); break; + + case I_CLIPS: additem(n, d->ammo[1], ammo, 1); break; + + case I_AMMO: additem(n, d->ammo[d->primary], ammo, d->primary); break; + + case I_GRENADE: additem(n, d->ammo[6], ammo, 6); break; + + case I_HEALTH: additem(n, d->health, np*5, 7); break; + + + + case I_ARMOUR: + + additem(n, d->armour, 20, 8); + + break; + + + + case I_QUAD: + + //additem(n, d->quadmillis, 60, 9); + + break; + + + + case CARROT: + + ents[n].spawned = false; + + triggertime = lastmillis; + + trigger(ents[n].attr1, ents[n].attr2, false); // needs to go over server for multiplayer + + break; + + + + }; + +}; + + + +void checkitems() + +{ + + if(editmode) return; + + loopv(ents) + + { + + entity &e = ents[i]; + + if(e.type==NOTUSED) continue; + + if(!ents[i].spawned) continue; + + if(OUTBORD(e.x, e.y)) continue; + + vec v = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; + + vdist(dist, t, player1->o, v); + + if(dist<2.5) pickup(i, player1); + + }; + +}; + + + +void putitems(uchar *&p) // puts items in network stream and also spawns them locally + +{ + + loopv(ents) if((ents[i].type>=I_CLIPS && ents[i].type<=I_QUAD) || ents[i].type==CARROT) + + { + + putint(p, i); + + ents[i].spawned = true; + + }; + +}; + + + +void resetspawns() { loopv(ents) ents[i].spawned = false; }; + +void setspawn(int i, bool on) { if(inextprimary!=i) d->ammo[i] = 0; + + d->mag[GUN_KNIFE] = 1; + + + + d->mag[GUN_PISTOL] = 8; + + d->ammo[GUN_PISTOL] = 24; + + + + if (m_pistol || d->primary==GUN_PISTOL) // || pistol only mode! + + { + + d->primary = GUN_PISTOL; + + d->ammo[d->primary] = 72; + + } + + else if (d->primary>GUN_PISTOL && d->primaryammo[d->primary] = itemstats[d->primary-1].max; + + else + + d->ammo[d->primary] = itemstats[d->primary-1].start; + + + + d->mag[d->primary] = itemstats[d->primary-1].add; + + } + + + +/* + + if (d->primary==GUN_GRENADE) + + { + + //conoutf("you don't have to worry about blowing your hand off just yet..."); + + } + +*/ + + if (d->hasarmour) + + { + + if(gamemode==m_arena) + + d->armour = 100; + + } + + + + d->gunselect = d->primary; //draw main weapon + +}; + + + +void item(int num) + +{ + + if (num>0 && num<7) player1->nextprimary = num; + + + + //if (num==6) conoutf("sorry, no nades yet"); + + + + if (num==7) { player1->hasarmour=!player1->hasarmour; /*toggle armour*/ }; + + + +}; + + + +void weapon(int num) + +{ + + if(num>0 && num<7) + { + player1->nextprimary = num; + player1->gunselect = num; + player1->primary = num; + + radd(player1); + } +} + + + + + +COMMAND(item, ARG_1INT); + +COMMAND(weapon, ARG_1INT); + diff --git a/source/src/physics.cpp b/source/src/physics.cpp index 1d2ee5db..9ef01478 100644 --- a/source/src/physics.cpp +++ b/source/src/physics.cpp @@ -1,483 +1,965 @@ -// physics.cpp: no physics books were hurt nor consulted in the construction of this code. -// All physics computations and constants were invented on the fly and simply tweaked until -// they "felt right", and have no basis in reality. Collision detection is simplistic but -// very robust (uses discrete steps at fixed fps). - -#include "cube.h" - -bool plcollide(dynent *d, dynent *o, float &headspace) // collide with player or monster -{ - if(o->state!=CS_ALIVE) return true; - const float r = o->radius+d->radius; - if(fabs(o->o.x-d->o.x)o.y-d->o.y)o.z-d->o.z)aboveeye+d->eyeheight) return false; - if(d->monsterstate) return false; // hack - headspace = d->o.z-o->o.z-o->aboveeye-d->eyeheight; - if(headspace<0) headspace = 10; - }; - return true; -}; - -bool cornertest(int mip, int x, int y, int dx, int dy, int &bx, int &by, int &bs) // recursively collide with a mipmapped corner cube -{ - sqr *w = wmip[mip]; - int sz = ssize>>mip; - bool stest = SOLID(SWS(w, x+dx, y, sz)) && SOLID(SWS(w, x, y+dy, sz)); - mip++; - x /= 2; - y /= 2; - if(SWS(wmip[mip], x, y, ssize>>mip)->type==CORNER) - { - bx = x<radius; - if(fabs(e.x-d->o.x)o.y)floor+mmi.zoff+e.attr3); - if(d->o.z-d->eyeheightlo) lo = mmz+mmi.h; - }; - }; -}; - -// all collision happens here -// spawn is a dirty side effect used in spawning -// drop & rise are supplied by the physics below to indicate gravity/push for current mini-timestep - -bool collide(dynent *d, bool spawn, float drop, float rise) -{ - const float fx1 = d->o.x-d->radius; // figure out integer cube rectangle this entity covers in map - const float fy1 = d->o.y-d->radius; - const float fx2 = d->o.x+d->radius; - const float fy2 = d->o.y+d->radius; - const int x1 = fast_f2nat(fx1); - const int y1 = fast_f2nat(fy1); - const int x2 = fast_f2nat(fx2); - const int y2 = fast_f2nat(fy2); - float hi = 127, lo = -128; - float minfloor = (d->monsterstate && !spawn && d->health>100) ? d->o.z-d->eyeheight-4.5f : -1000.0f; // big monsters are afraid of heights, unless angry :) - - for(int x = x1; x<=x2; x++) for(int y = y1; y<=y2; y++) // collide with map - { - if(OUTBORD(x,y)) return false; - sqr *s = S(x,y); - float ceil = s->ceil; - float floor = s->floor; - switch(s->type) - { - case SOLID: - return false; - - case CORNER: - { - int bx = x, by = y, bs = 1; - if(x==x1 && y==y1 && cornertest(0, x, y, -1, -1, bx, by, bs) && fx1-bx+fy1-by<=bs - || x==x2 && y==y1 && cornertest(0, x, y, 1, -1, bx, by, bs) && fx2-bx>=fy1-by - || x==x1 && y==y2 && cornertest(0, x, y, -1, 1, bx, by, bs) && fx1-bx<=fy2-by - || x==x2 && y==y2 && cornertest(0, x, y, 1, 1, bx, by, bs) && fx2-bx+fy2-by>=bs) - return false; - break; - }; - - case FHF: // FIXME: too simplistic collision with slopes, makes it feels like tiny stairs - floor -= (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; - break; - - case CHF: - ceil += (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; - - }; - if(ceillo) lo = floor; - if(flooreyeheight+d->aboveeye) return false; - - float headspace = 10; - loopv(players) // collide with other players - { - dynent *o = players[i]; - if(!o || o==d) continue; - if(!plcollide(d, o, headspace)) return false; - }; - if(d!=player1 && d->mtype!=M_NADE) if(!plcollide(d, player1, headspace)) return false; - headspace -= 0.01f; - - mmcollide(d, hi, lo); // collide with map models - - if(spawn) - { - d->o.z = lo+d->eyeheight; // just drop to floor (sideeffect) - d->startheight = d->o.z; //no falling damage on spawn - d->onfloor = true; - } - else - { - const float space = d->o.z-d->eyeheight-lo; - if(space<0) - { - if(space>-0.01) - { - d->o.z = lo+d->eyeheight; // stick on step - } - else if(space>-1.26f) d->o.z += rise; // rise thru stair - else return false; - } - else - { - d->o.z -= min(min(drop, space), headspace); // gravity - }; - - const float space2 = hi-(d->o.z+d->aboveeye); - if(space2<0) - { - if(space2<-0.1) return false; // hack alert! - d->o.z = hi-d->aboveeye; // glue to ceiling - d->vel.z = 0; // cancel out jumping velocity - }; - - d->onfloor = d->o.z-d->eyeheight-lo<0.01f; - }; - return true; -} - -float rad(float x) { return x*3.14159f/180; }; - -VAR(maxroll, 0, 3, 20); - -int physicsfraction = 0, physicsrepeat = 0; -const int MINFRAMETIME = 20; // physics always simulated at 50fps or better - -void physicsframe() // optimally schedule physics frames inside the graphics frames -{ - if(curtime>=MINFRAMETIME) - { - int faketime = curtime+physicsfraction; - physicsrepeat = faketime/MINFRAMETIME; - physicsfraction = faketime-physicsrepeat*MINFRAMETIME; - } - else - { - physicsrepeat = 1; - }; -}; - -void selfdamage(dynent * d, int dm) -{ - if(d==player1) selfdamage(dm, -1, d); - else { addmsg(1, 4, SV_DAMAGE, d, dm, d->lifesequence); playsound(S_FALL1+rnd(5), &d->o); }; - particle_splash(3, dm, 1000, d->o); //edit out? - demodamage(dm, d->o); -}; - - -// main physics routine, moves a player/monster for a curtime step -// moveres indicated the physics precision (which is lower for monsters and multiplayer prediction) -// local is false for multiplayer prediction - -void moveplayer(dynent *pl, int moveres, bool local, int curtime) -{ - const bool water = hdr.waterlevel>pl->o.z-0.5f; - const bool floating = (editmode && local) || pl->state==CS_EDITING; - - vec d; // vector of direction we ideally want to move in - - d.x = (float)(pl->move*cos(rad(pl->yaw-90))); - d.y = (float)(pl->move*sin(rad(pl->yaw-90))); - d.z = (float) pl->bounce ? pl->vel.z : 0; - - if(floating || water) - { - d.x *= (float)cos(rad(pl->pitch)); - d.y *= (float)cos(rad(pl->pitch)); - d.z = (float)(pl->move*sin(rad(pl->pitch))); - }; - - d.x += (float)(pl->strafe*cos(rad(pl->yaw-180))); - d.y += (float)(pl->strafe*sin(rad(pl->yaw-180))); - - const float speed = curtime/(water ? 2000.0f : 1000.0f)*pl->maxspeed; - const float friction = water ? 20.0f : (pl->onfloor || floating ? 6.0f : 30.0f); - - const float fpsfric = friction/curtime*20.0f; - - vmul(pl->vel, fpsfric-1); // slowly apply friction and direction to velocity, gives a smooth movement - vadd(pl->vel, d); - vdiv(pl->vel, fpsfric); - d = pl->vel; - vmul(d, speed); // d is now frametime based velocity vector - - pl->blocked = false; - pl->moving = true; - - if(floating) // just apply velocity - { - vadd(pl->o, d); - if(pl->jumpnext) { pl->jumpnext = false; pl->vel.z = 2; } - } - else // apply velocity with collision - { - if(pl->onfloor || water) - { - if (pl->onfloor || !pl->vel.z) - { - int damage = (int)pl->startheight - (int)pl->o.z; - if (damage > 8 && local) //try adding "&& local" if falling not fixed - { - selfdamage(damage,-1,pl); - demodamage(damage, player1->o); - playsound(S_FALL1+rnd(2), &pl->o); - }; - pl->startheight=pl->o.z; - }; - - if(pl->jumpnext) - { - pl->jumpnext = false; - pl->vel.z = 1.7f; // physics impulse upwards - if(water) { pl->vel.x /= 8; pl->vel.y /= 8; }; // dampen velocity change even harder, gives correct water feel - if(local) playsoundc(S_JUMP); - else if(pl->monsterstate) playsound(S_JUMP, &pl->o); - } - else if(pl->timeinair>800) // if we land after long time must have been a high jump, make thud sound - { - if(local) playsoundc(S_LAND); - else if(pl->monsterstate) playsound(S_LAND, &pl->o); - }; - pl->timeinair = 0; - if(pl->bounce) pl->vel.z *= 0.7f; - } - else - { - pl->timeinair += curtime; - }; - - const float gravity = pl->bounce ? pl->gravity : 20; - const float f = 1.0f/moveres; - float dropf = pl->bounce ? ((gravity-1)+pl->timeinair/14.0f) : ((gravity-1)+pl->timeinair/15.0f); // incorrect, but works fine - if(water) { dropf = 5; pl->timeinair = 0; }; // float slowly down in water - float drop = dropf*curtime/gravity/100/moveres; // at high fps, gravity kicks in too fast - const float rise = speed/moveres/1.2f; // extra smoothness when lifting up stairs - loopi(moveres) // discrete steps collision detection & sliding - { - // try move forward - pl->o.x += f*d.x; - pl->o.y += f*d.y; - pl->o.z += f*d.z; - if(collide(pl, false, drop, rise)) continue; - // player stuck, try slide along y axis - pl->blocked = true; - pl->o.x -= f*d.x; - if(collide(pl, false, drop, rise)) - { - d.x = 0; - if(pl->bounce) pl->vel.x = -pl->vel.x; - continue; - }; - pl->o.x += f*d.x; - // still stuck, try x axis - pl->o.y -= f*d.y; - if(collide(pl, false, drop, rise)) - { - d.y = 0; - if(pl->bounce) pl->vel.y = -pl->vel.y; - continue; - }; - pl->o.y += f*d.y; - // try just dropping down - pl->moving = false; - pl->o.x -= f*d.x; - pl->o.y -= f*d.y; - if(collide(pl, false, drop, rise)) - { - d.y = d.x = 0; - continue; - }; - pl->o.z -= f*d.z; - break; - }; - }; - - // detect wether player is outside map, used for skipping zbuffer clear mostly - - if(pl->o.x < 0 || pl->o.x >= ssize || pl->o.y <0 || pl->o.y > ssize) - { - pl->outsidemap = true; - } - else - { - sqr *s = S((int)pl->o.x, (int)pl->o.y); - pl->outsidemap = SOLID(s) - || pl->o.z < s->floor - (s->type==FHF ? s->vdelta/4 : 0) - || pl->o.z > s->ceil + (s->type==CHF ? s->vdelta/4 : 0); - }; - - // automatically apply smooth roll when strafing - - if(pl->strafe==0) - { - pl->roll = pl->roll/(1+(float)sqrt((float)curtime)/25); - } - else - { - pl->roll += pl->strafe*curtime/-30.0f; - if(pl->roll>maxroll) pl->roll = (float)maxroll; - if(pl->roll<-maxroll) pl->roll = (float)-maxroll; - }; - - // play sounds on water transitions - - if(!pl->inwater && water) { playsound(S_SPLASH2, &pl->o); pl->vel.z = 0; } - else if(pl->inwater && !water) playsound(S_SPLASH1, &pl->o); - pl->inwater = water; -}; - -void moveplayer(dynent *pl, int moveres, bool local) -{ - loopi(physicsrepeat) moveplayer(pl, moveres, local, i ? curtime/physicsrepeat : curtime-curtime/physicsrepeat*(physicsrepeat-1)); -}; - -void movephysent(physent *pl) -{ - int moveres = 2; - bool local=true; - loopj(physicsrepeat) - { - const bool water = hdr.waterlevel>pl->o.z-0.5f; - const bool floating = (editmode && local); - - vec d; // vector of direction we ideally want to move in - - d = pl->vel; - - - const float speed = curtime/(water ? 2000.0f : 1000.0f)*pl->maxspeed; - const float friction = water ? 20.0f : (pl->onfloor || floating ? 6.0f : 30.0f); - - const float fpsfric = friction/curtime*20.0f; - - vmul(pl->vel, fpsfric-1); // slowly apply friction and direction to velocity, gives a smooth movement - vadd(pl->vel, d); - vdiv(pl->vel, fpsfric); - d = pl->vel; - vmul(d, speed); // d is now frametime based velocity vector - - -/* if(floating || water) - { - d.x *= (float)cos(rad(pl->pitch)); - d.y *= (float)cos(rad(pl->pitch)); - d.z = (float)(pl->move*sin(rad(pl->pitch))); - };*/ - - /*if(pl->onfloor || water) - { - if(pl->jumpnext) - { - pl->jumpnext = false; - pl->vel.z = 1.7f; // physics impulse upwards - if(water) { pl->vel.x /= 8; pl->vel.y /= 8; }; // dampen velocity change even harder, gives correct water feel - //if(local) playsoundc(S_JUMP); - } - else if(pl->timeinair>800) // if we land after long time must have been a high jump, make thud sound - { - //if(local) playsoundc(S_LAND); - //else if(pl->monsterstate) playsound(S_LAND, &pl->o); - }; - pl->timeinair = 0; - } - else - { - pl->timeinair += curtime; - };*/ - - if(pl->onfloor) - { - pl->timeinair = 0; - vmul(pl->vel, 0.8f); - } - else - { - pl->timeinair += curtime; - pl->pitch = (lastmillis-pl->millis) % 360; - pl->yaw = pl->pitch; - }; - - - //const float speed = curtime/(water ? 2000.0f : 1000.0f)*pl->maxspeed; - - const float gravity = 20; - const float f = 1.0f/moveres; - float dropf = pl->timeinair/15.0f; // incorrect, but works fine - if(water) { dropf = 5; pl->timeinair = 0; }; // float slowly down in water - const float drop = dropf*curtime/gravity/100/moveres; // at high fps, gravity kicks in too fast - const float rise = speed/moveres/1.2f; // extra smoothness when lifting up stairs -// if(pl->mtype = M_NADE)printf("%i\n", pl->onfloor); -/* d.x -= d.x/50.0f*curtime; - d.y -= d.y/50.0f*curtime;*/ - - - loopi(moveres) // discrete steps collision detection & sliding - { - // try move forward - pl->o.x += f*d.x; - pl->o.y += f*d.y; - pl->o.z += f*d.z; - if(collide(pl, false, drop, rise)) continue; - // player stuck, try slide along y axis - pl->blocked = true; - pl->o.x -= f*d.x; - if(collide(pl, false, drop, rise)) { pl->vel.x = -d.x; vmul(pl->vel, 0.8f); d.x = 0; continue; }; - pl->o.x += f*d.x; - // still stuck, try x axis - pl->o.y -= f*d.y; - if(collide(pl, false, drop, rise)) { pl->vel.y = -d.y; vmul(pl->vel, 0.8f); d.y = 0; continue; }; - pl->o.y += f*d.y; - // try just dropping down - pl->moving = false; - pl->o.x -= f*d.x; - pl->o.y -= f*d.y; - if(collide(pl, false, drop, rise)) { d.y = d.x = 0; vmul(pl->vel, 0.8f); continue; }; - pl->o.z -= f*d.z; - break; - }; - - // detect wether player is outside map, used for skipping zbuffer clear mostly - - - - if(pl->o.x < 0 || pl->o.x >= ssize || pl->o.y <0 || pl->o.y > ssize) - { - pl->outsidemap = true; - } - else - { - sqr *s = S((int)pl->o.x, (int)pl->o.y); - pl->outsidemap = SOLID(s) - || pl->o.z < s->floor - (s->type==FHF ? s->vdelta/4 : 0) - || pl->o.z > s->ceil + (s->type==CHF ? s->vdelta/4 : 0); - }; - - // play sounds on water transitions - - if(!pl->inwater && water) { playsound(S_SPLASH2, &pl->o); pl->vel.z = 0; } - else if(pl->inwater && !water) playsound(S_SPLASH1, &pl->o); - pl->inwater = water; - }; -}; +// physics.cpp: no physics books were hurt nor consulted in the construction of this code. + +// All physics computations and constants were invented on the fly and simply tweaked until + +// they "felt right", and have no basis in reality. Collision detection is simplistic but + +// very robust (uses discrete steps at fixed fps). + + + +#include "cube.h" + + + +bool plcollide(dynent *d, dynent *o, float &headspace) // collide with player or monster + +{ + + if(o->state!=CS_ALIVE) return true; + + const float r = o->radius+d->radius; + + if(fabs(o->o.x-d->o.x)o.y-d->o.y)o.z-d->o.z)aboveeye+d->eyeheight) return false; + + if(d->monsterstate) return false; // hack + + headspace = d->o.z-o->o.z-o->aboveeye-d->eyeheight; + + if(headspace<0) headspace = 10; + + }; + + return true; + +}; + + + +bool cornertest(int mip, int x, int y, int dx, int dy, int &bx, int &by, int &bs) // recursively collide with a mipmapped corner cube + +{ + + sqr *w = wmip[mip]; + + int sz = ssize>>mip; + + bool stest = SOLID(SWS(w, x+dx, y, sz)) && SOLID(SWS(w, x, y+dy, sz)); + + mip++; + + x /= 2; + + y /= 2; + + if(SWS(wmip[mip], x, y, ssize>>mip)->type==CORNER) + + { + + bx = x<radius; + + if(fabs(e.x-d->o.x)o.y)floor+mmi.zoff+e.attr3); + + if(d->o.z-d->eyeheightlo) lo = mmz+mmi.h; + + }; + + }; + +}; + + + +// all collision happens here + +// spawn is a dirty side effect used in spawning + +// drop & rise are supplied by the physics below to indicate gravity/push for current mini-timestep + + + +bool collide(dynent *d, bool spawn, float drop, float rise) + +{ + + const float fx1 = d->o.x-d->radius; // figure out integer cube rectangle this entity covers in map + + const float fy1 = d->o.y-d->radius; + + const float fx2 = d->o.x+d->radius; + + const float fy2 = d->o.y+d->radius; + + const int x1 = fast_f2nat(fx1); + + const int y1 = fast_f2nat(fy1); + + const int x2 = fast_f2nat(fx2); + + const int y2 = fast_f2nat(fy2); + + float hi = 127, lo = -128; + + float minfloor = (d->monsterstate && !spawn && d->health>100) ? d->o.z-d->eyeheight-4.5f : -1000.0f; // big monsters are afraid of heights, unless angry :) + + + + for(int x = x1; x<=x2; x++) for(int y = y1; y<=y2; y++) // collide with map + + { + + if(OUTBORD(x,y)) return false; + + sqr *s = S(x,y); + + float ceil = s->ceil; + + float floor = s->floor; + + switch(s->type) + + { + + case SOLID: + + return false; + + + + case CORNER: + + { + + int bx = x, by = y, bs = 1; + + if(x==x1 && y==y1 && cornertest(0, x, y, -1, -1, bx, by, bs) && fx1-bx+fy1-by<=bs + + || x==x2 && y==y1 && cornertest(0, x, y, 1, -1, bx, by, bs) && fx2-bx>=fy1-by + + || x==x1 && y==y2 && cornertest(0, x, y, -1, 1, bx, by, bs) && fx1-bx<=fy2-by + + || x==x2 && y==y2 && cornertest(0, x, y, 1, 1, bx, by, bs) && fx2-bx+fy2-by>=bs) + + return false; + + break; + + }; + + + + case FHF: // FIXME: too simplistic collision with slopes, makes it feels like tiny stairs + + floor -= (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; + + break; + + + + case CHF: + + ceil += (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; + + + + }; + + if(ceillo) lo = floor; + + if(flooreyeheight+d->aboveeye) return false; + + + + float headspace = 10; + + loopv(players) // collide with other players + + { + + dynent *o = players[i]; + + if(!o || o==d) continue; + + if(!plcollide(d, o, headspace)) return false; + + }; + + if(d!=player1 && d->mtype!=M_NADE) if(!plcollide(d, player1, headspace)) return false; + + headspace -= 0.01f; + + + + mmcollide(d, hi, lo); // collide with map models + + + + if(spawn) + + { + + d->o.z = lo+d->eyeheight; // just drop to floor (sideeffect) + + d->startheight = d->o.z; //no falling damage on spawn + + d->onfloor = true; + + } + + else + + { + + const float space = d->o.z-d->eyeheight-lo; + + if(space<0) + + { + + if(space>-0.01) + + { + + d->o.z = lo+d->eyeheight; // stick on step + + } + + else if(space>-1.26f) d->o.z += rise; // rise thru stair + + else return false; + + } + + else + + { + + d->o.z -= min(min(drop, space), headspace); // gravity + + }; + + + + const float space2 = hi-(d->o.z+d->aboveeye); + + if(space2<0) + + { + + if(space2<-0.1) return false; // hack alert! + + d->o.z = hi-d->aboveeye; // glue to ceiling + + d->vel.z = 0; // cancel out jumping velocity + + }; + + + + d->onfloor = d->o.z-d->eyeheight-lo<0.01f; + + }; + + return true; + +} + + + +float rad(float x) { return x*3.14159f/180; }; + + + +VAR(maxroll, 0, 3, 20); + + + +int physicsfraction = 0, physicsrepeat = 0; + +const int MINFRAMETIME = 20; // physics always simulated at 50fps or better + + + +void physicsframe() // optimally schedule physics frames inside the graphics frames + +{ + + if(curtime>=MINFRAMETIME) + + { + + int faketime = curtime+physicsfraction; + + physicsrepeat = faketime/MINFRAMETIME; + + physicsfraction = faketime-physicsrepeat*MINFRAMETIME; + + } + + else + + { + + physicsrepeat = 1; + + }; + +}; + + + +void selfdamage(dynent * d, int dm) + +{ + + if(d==player1) selfdamage(dm, -1, d); + + else { addmsg(1, 4, SV_DAMAGE, d, dm, d->lifesequence); playsound(S_FALL1+rnd(5), &d->o); }; + + particle_splash(3, dm, 1000, d->o); //edit out? + + demodamage(dm, d->o); + +}; + + + + + +// main physics routine, moves a player/monster for a curtime step + +// moveres indicated the physics precision (which is lower for monsters and multiplayer prediction) + +// local is false for multiplayer prediction + + + +void moveplayer(dynent *pl, int moveres, bool local, int curtime) + +{ + + const bool water = hdr.waterlevel>pl->o.z-0.5f; + + const bool floating = (editmode && local) || pl->state==CS_EDITING; + + + + vec d; // vector of direction we ideally want to move in + + + + d.x = (float)(pl->move*cos(rad(pl->yaw-90))); + + d.y = (float)(pl->move*sin(rad(pl->yaw-90))); + + d.z = (float) pl->bounce ? pl->vel.z : 0; + + + + if(floating || water) + + { + + d.x *= (float)cos(rad(pl->pitch)); + + d.y *= (float)cos(rad(pl->pitch)); + + d.z = (float)(pl->move*sin(rad(pl->pitch))); + + }; + + + + d.x += (float)(pl->strafe*cos(rad(pl->yaw-180))); + + d.y += (float)(pl->strafe*sin(rad(pl->yaw-180))); + + + + const float speed = curtime/(water ? 2000.0f : 1000.0f)*pl->maxspeed; + + const float friction = water ? 20.0f : (pl->onfloor || floating ? 6.0f : 30.0f); + + + + const float fpsfric = friction/curtime*20.0f; + + + + vmul(pl->vel, fpsfric-1); // slowly apply friction and direction to velocity, gives a smooth movement + + vadd(pl->vel, d); + + vdiv(pl->vel, fpsfric); + + d = pl->vel; + + vmul(d, speed); // d is now frametime based velocity vector + + + + pl->blocked = false; + + pl->moving = true; + + + + if(floating) // just apply velocity + + { + + vadd(pl->o, d); + + if(pl->jumpnext) { pl->jumpnext = false; pl->vel.z = 2; } + + } + + else // apply velocity with collision + + { + + if(pl->onfloor || water) + + { + + if (pl->onfloor || !pl->vel.z) + + { + + int damage = (int)pl->startheight - (int)pl->o.z; + + if (damage > 8 && local) //try adding "&& local" if falling not fixed + + { + + selfdamage(damage,-1,pl); + + demodamage(damage, player1->o); + + playsound(S_FALL1+rnd(2), &pl->o); + + }; + + pl->startheight=pl->o.z; + + }; + + + + if(pl->jumpnext) + + { + + pl->jumpnext = false; + + pl->vel.z = 1.7f; // physics impulse upwards + + if(water) { pl->vel.x /= 8; pl->vel.y /= 8; }; // dampen velocity change even harder, gives correct water feel + + if(local) playsoundc(S_JUMP); + + else if(pl->monsterstate) playsound(S_JUMP, &pl->o); + + } + + else if(pl->timeinair>800) // if we land after long time must have been a high jump, make thud sound + + { + + if(local) playsoundc(S_LAND); + + else if(pl->monsterstate) playsound(S_LAND, &pl->o); + + }; + + pl->timeinair = 0; + + if(pl->bounce) pl->vel.z *= 0.7f; + + } + + else + + { + + pl->timeinair += curtime; + + }; + + const float gravity = pl->bounce ? pl->gravity : 20; + + const float f = 1.0f/moveres; + + float dropf = pl->bounce ? ((gravity-1)+pl->timeinair/14.0f) : ((gravity-1)+pl->timeinair/15.0f); // incorrect, but works fine + + if(water) { dropf = 5; pl->timeinair = 0; }; // float slowly down in water + + float drop = dropf*curtime/gravity/100/moveres; // at high fps, gravity kicks in too fast + + const float rise = speed/moveres/1.2f; // extra smoothness when lifting up stairs + + + loopi(moveres) // discrete steps collision detection & sliding + + { + + // try move forward + + pl->o.x += f*d.x; + + pl->o.y += f*d.y; + + pl->o.z += f*d.z; + + if(collide(pl, false, drop, rise)) continue; + + // player stuck, try slide along y axis + + pl->blocked = true; + + pl->o.x -= f*d.x; + + if(collide(pl, false, drop, rise)) + + { + + d.x = 0; + + if(pl->bounce) pl->vel.x = -pl->vel.x; + + continue; + + }; + + pl->o.x += f*d.x; + + // still stuck, try x axis + + pl->o.y -= f*d.y; + + if(collide(pl, false, drop, rise)) + + { + + d.y = 0; + + if(pl->bounce) pl->vel.y = -pl->vel.y; + + continue; + + }; + + pl->o.y += f*d.y; + + // try just dropping down + + pl->moving = false; + + pl->o.x -= f*d.x; + + pl->o.y -= f*d.y; + + if(collide(pl, false, drop, rise)) + + { + + d.y = d.x = 0; + + continue; + + }; + + pl->o.z -= f*d.z; + + break; + + }; + + }; + + + + // detect wether player is outside map, used for skipping zbuffer clear mostly + + + + if(pl->o.x < 0 || pl->o.x >= ssize || pl->o.y <0 || pl->o.y > ssize) + + { + + pl->outsidemap = true; + + } + + else + + { + + sqr *s = S((int)pl->o.x, (int)pl->o.y); + + pl->outsidemap = SOLID(s) + + || pl->o.z < s->floor - (s->type==FHF ? s->vdelta/4 : 0) + + || pl->o.z > s->ceil + (s->type==CHF ? s->vdelta/4 : 0); + + }; + + + + // automatically apply smooth roll when strafing + + + + if(pl->strafe==0) + + { + + pl->roll = pl->roll/(1+(float)sqrt((float)curtime)/25); + + } + + else + + { + + pl->roll += pl->strafe*curtime/-30.0f; + + if(pl->roll>maxroll) pl->roll = (float)maxroll; + + if(pl->roll<-maxroll) pl->roll = (float)-maxroll; + + }; + + + + // play sounds on water transitions + + + + if(!pl->inwater && water) { playsound(S_SPLASH2, &pl->o); pl->vel.z = 0; } + + else if(pl->inwater && !water) playsound(S_SPLASH1, &pl->o); + + pl->inwater = water; + +}; + + + +void moveplayer(dynent *pl, int moveres, bool local) + +{ + + loopi(physicsrepeat) moveplayer(pl, moveres, local, i ? curtime/physicsrepeat : curtime-curtime/physicsrepeat*(physicsrepeat-1)); + +}; + + + +void movephysent(physent *pl) + +{ + + int moveres = 2; + + bool local=true; + + loopj(physicsrepeat) + + { + + const bool water = hdr.waterlevel>pl->o.z-0.5f; + + const bool floating = (editmode && local); + + + + vec d; // vector of direction we ideally want to move in + + + + d = pl->vel; + + + + + + const float speed = curtime/(water ? 2000.0f : 1000.0f)*pl->maxspeed; + + const float friction = water ? 20.0f : (pl->onfloor || floating ? 6.0f : 30.0f); + + + + const float fpsfric = friction/curtime*20.0f; + + + + vmul(pl->vel, fpsfric-1); // slowly apply friction and direction to velocity, gives a smooth movement + + vadd(pl->vel, d); + + vdiv(pl->vel, fpsfric); + + d = pl->vel; + + vmul(d, speed); // d is now frametime based velocity vector + + + + + +/* if(floating || water) + + { + + d.x *= (float)cos(rad(pl->pitch)); + + d.y *= (float)cos(rad(pl->pitch)); + + d.z = (float)(pl->move*sin(rad(pl->pitch))); + + };*/ + + + + /*if(pl->onfloor || water) + + { + + if(pl->jumpnext) + + { + + pl->jumpnext = false; + + pl->vel.z = 1.7f; // physics impulse upwards + + if(water) { pl->vel.x /= 8; pl->vel.y /= 8; }; // dampen velocity change even harder, gives correct water feel + + //if(local) playsoundc(S_JUMP); + + } + + else if(pl->timeinair>800) // if we land after long time must have been a high jump, make thud sound + + { + + //if(local) playsoundc(S_LAND); + + //else if(pl->monsterstate) playsound(S_LAND, &pl->o); + + }; + + pl->timeinair = 0; + + } + + else + + { + + pl->timeinair += curtime; + + };*/ + + + + if(pl->onfloor) + + { + + pl->timeinair = 0; + + vmul(pl->vel, 0.8f); + + } + + else + + { + + pl->timeinair += curtime; + + pl->pitch = (lastmillis-pl->millis) % 360; + + pl->yaw = pl->pitch; + + }; + + + + + + //const float speed = curtime/(water ? 2000.0f : 1000.0f)*pl->maxspeed; + + + + const float gravity = 20; + + const float f = 1.0f/moveres; + + float dropf = pl->timeinair/15.0f; // incorrect, but works fine + + if(water) { dropf = 5; pl->timeinair = 0; }; // float slowly down in water + + const float drop = dropf*curtime/gravity/100/moveres; // at high fps, gravity kicks in too fast + + const float rise = speed/moveres/1.2f; // extra smoothness when lifting up stairs + +// if(pl->mtype = M_NADE)printf("%i\n", pl->onfloor); + +/* d.x -= d.x/50.0f*curtime; + + d.y -= d.y/50.0f*curtime;*/ + + + + + + loopi(moveres) // discrete steps collision detection & sliding + + { + + // try move forward + + pl->o.x += f*d.x; + + pl->o.y += f*d.y; + + pl->o.z += f*d.z; + + if(collide(pl, false, drop, rise)) continue; + + // player stuck, try slide along y axis + + pl->blocked = true; + + pl->o.x -= f*d.x; + + if(collide(pl, false, drop, rise)) { pl->vel.x = -d.x; vmul(pl->vel, 0.8f); d.x = 0; continue; }; + + pl->o.x += f*d.x; + + // still stuck, try x axis + + pl->o.y -= f*d.y; + + if(collide(pl, false, drop, rise)) { pl->vel.y = -d.y; vmul(pl->vel, 0.8f); d.y = 0; continue; }; + + pl->o.y += f*d.y; + + // try just dropping down + + pl->moving = false; + + pl->o.x -= f*d.x; + + pl->o.y -= f*d.y; + + if(collide(pl, false, drop, rise)) { d.y = d.x = 0; vmul(pl->vel, 0.8f); continue; }; + + pl->o.z -= f*d.z; + + break; + + }; + + + + // detect wether player is outside map, used for skipping zbuffer clear mostly + + + + + + + + if(pl->o.x < 0 || pl->o.x >= ssize || pl->o.y <0 || pl->o.y > ssize) + + { + + pl->outsidemap = true; + + } + + else + + { + + sqr *s = S((int)pl->o.x, (int)pl->o.y); + + pl->outsidemap = SOLID(s) + + || pl->o.z < s->floor - (s->type==FHF ? s->vdelta/4 : 0) + + || pl->o.z > s->ceil + (s->type==CHF ? s->vdelta/4 : 0); + + }; + + + + // play sounds on water transitions + + + + if(!pl->inwater && water) { playsound(S_SPLASH2, &pl->o); pl->vel.z = 0; } + + else if(pl->inwater && !water) playsound(S_SPLASH1, &pl->o); + + pl->inwater = water; + + }; + +}; + diff --git a/source/src/rendermd3.cpp b/source/src/rendermd3.cpp index 98fea7cb..a954aba2 100644 --- a/source/src/rendermd3.cpp +++ b/source/src/rendermd3.cpp @@ -1,516 +1,1032 @@ -// code for loading, linking and rendering md3 models -// See http://www.draekko.org/documentation/md3format.html for informations about the md3 format - -#include "cube.h" - -const int FIRSTMD3 = 20; -enum { MDL_LOWER = 0, MDL_UPPER, MDL_HEAD }; - -#define MD3_DEFAULT_SCALE 0.08f - -struct vec2 -{ - float x, y; -}; - -struct md3tag -{ - char name[64]; - float pos[3]; - float rotation[3][3]; -}; - -struct md3vertex -{ - signed short vertex[3]; - uchar normal[2]; -}; - -struct md3triangle -{ - int vertexindices[3]; -}; - -struct md3animinfo -{ - int start; - int end; - bool loop; - int fps; -}; - -struct md3header -{ - char id[4]; - int version; - char name[64]; - int flags; - int numframes, numtags, nummeshes, numskins; - int ofs_frames, ofs_tags, ofs_meshes, ofs_eof; // offsets -}; - -struct md3meshheader -{ - char id[4]; - char name[64]; - int flags; - int numframes, numshaders, numvertices, numtriangles; - int ofs_triangles, ofs_shaders, ofs_uv, ofs_vertices, meshsize; // offsets -}; - -struct md3mesh -{ - char name[64]; - md3triangle *triangles; - vec *vertices; - vec2 *uv; - int numtriangles, numvertices; // numvertices == numUV - int tex; -}; - -struct md3model -{ - vector meshes; - vector animations; - md3state *animstate; - md3model **links; - md3tag *tags; - int numframes, numtags; - bool loaded; - vec scale; - bool link(md3model *link, char *tag); - bool load(char *path); - void render(); - void draw(float x, float y, float z, float yaw, float pitch, float rad, vec &light); - md3model(); - ~md3model(); -}; - -md3model::md3model() -{ - loaded = false; - scale.x = scale.y = scale.z = MD3_DEFAULT_SCALE; - animstate = NULL; -}; - -md3model::~md3model() -{ - loopv(meshes){ - if(meshes[i].vertices) delete [] meshes[i].vertices; - if(meshes[i].triangles) delete [] meshes[i].triangles; - if(meshes[i].uv) delete [] meshes[i].uv; - }; - if(links) free(links); - if(tags) delete [] tags; -}; - -bool md3model::link(md3model *link, char *tag) -{ - loopi(numtags) - if(strcmp(tags[i].name, tag) == 0) - { - links[i] = link; - return true; - }; - return false; -}; - -bool md3model::load(char *path) -{ - if(!path) return false; - FILE *f = fopen(path, "rb"); - if(!f) fatal("could not load md3 model: %s", path); - md3header header; - fread(&header, sizeof(md3header), 1, f); - if(header.id[0] != 'I' || header.id[1] != 'D' || header.id[2] != 'P' || header.id[3] != '3' || header.version != 15) // header check - fatal("corruped header in md3 model: %s", path); - - tags = new md3tag[header.numframes * header.numtags]; - fseek(f, header.ofs_tags, SEEK_SET); - fread(tags, sizeof(md3tag), header.numframes * header.numtags, f); - numframes = header.numframes; - numtags = header.numtags; - - links = (md3model **) malloc(sizeof(md3model) * header.numtags); - loopi(header.numtags) links[i] = NULL; - - int mesh_offset = ftell(f); - - loopi(header.nummeshes) - { - md3mesh mesh; - md3meshheader mheader; - fseek(f, mesh_offset, SEEK_SET); - fread(&mheader, sizeof(md3meshheader), 1, f); - strn0cpy(mesh.name, mheader.name, 64); - - mesh.triangles = new md3triangle[mheader.numtriangles]; - fseek(f, mesh_offset + mheader.ofs_triangles, SEEK_SET); - fread(mesh.triangles, sizeof(md3triangle), mheader.numtriangles, f); // read the triangles - mesh.numtriangles = mheader.numtriangles; - - mesh.uv = new vec2[mheader.numvertices]; - fseek(f, mesh_offset + mheader.ofs_uv , SEEK_SET); - fread(mesh.uv, sizeof(vec2), mheader.numvertices, f); // read the UV data - - md3vertex *vertices = new md3vertex[mheader.numframes * mheader.numvertices]; - fseek(f, mesh_offset + mheader.ofs_vertices, SEEK_SET); - fread(vertices, sizeof(md3vertex), mheader.numframes * mheader.numvertices, f); // read the vertices - mesh.vertices = new vec[mheader.numframes * mheader.numvertices]; // transform to our own structure - loopj(mheader.numframes * mheader.numvertices) - { - mesh.vertices[j].x = vertices[j].vertex[0] / 64.0f; - mesh.vertices[j].y = vertices[j].vertex[1] / 64.0f; - mesh.vertices[j].z = vertices[j].vertex[2] / 64.0f; - }; - mesh.numvertices = mheader.numvertices; - - meshes.add(mesh); - mesh_offset += mheader.meshsize; - delete [] vertices; - }; - - loaded = true; - return true; -}; - -VAR(anim, 0, 0, 100); - -void md3model::render() -{ - if(!loaded || meshes.length() <= 0) return; - float t = 0.0f; - int nextfrm; - 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 - if(t >= 1.0f) // jump to the next keyframe - { - a->frm++; - a->lastTime = lastmillis; - t = 0.0f; - }; - if(a->frm >= info->end) - { - if(info->loop) - a->frm = info->start; - else - { - a->frm = info->end; - t = 0.0f; // stops the animation - }; - }; - } - else - { - a = new md3state(); - a->frm = 0; - } - a->frm = anim; //fixme - nextfrm = a->frm + 1; - - #define interpolate(p1,p2) ((p1) + t * ((p2) - (p1))) - - loopv(meshes) - { - md3mesh *mesh = &meshes[i]; - - glBindTexture(GL_TEXTURE_2D, FIRSTMD3 + mesh->tex); - - glBegin(GL_TRIANGLES); - loopj(mesh->numtriangles) // triangles - { - loopk(3) // vertices - { - int index = mesh->triangles[j].vertexindices[k]; - vec *point1 = &mesh->vertices[index + a->frm * mesh->numvertices]; - vec *point2 = &mesh->vertices[index + nextfrm * mesh->numvertices]; - - if(mesh->uv) - glTexCoord2f(mesh->uv[index].x, mesh->uv[index].y); - - glVertex3f( interpolate(point1->x, point2->x), - interpolate(point1->y, point2->y), - interpolate(point1->z, point2->z)); - }; - }; - glEnd(); - }; - - loopi(numtags) // render the linked models - interpolate rotation and position of the 'link-tags' - { - md3model *link = links[i]; - if(!link) continue; - GLfloat matrix[16] = {0}; // fixme: un-obfuscate it! - loopj(3) matrix[j] = interpolate(tags[a->frm * numtags + i].rotation[0][j], tags[nextfrm * numtags + i].rotation[0][j]); // rotation - loopj(3) matrix[4 + j] = interpolate(tags[a->frm * numtags + i].rotation[1][j], tags[nextfrm * numtags + i].rotation[1][j]); - loopj(3) matrix[8 + j] = interpolate(tags[a->frm * numtags + i].rotation[2][j], tags[nextfrm * numtags + i].rotation[2][j]); - loopj(3) matrix[12 + j] = interpolate(tags[a->frm * numtags + i].pos[j] , tags[nextfrm * numtags + i].pos[j]); // position - matrix[15] = 1.0f; - glPushMatrix(); - glMultMatrixf(matrix); - link->render(); - glPopMatrix(); - }; -}; - -void md3model::draw(float x, float y, float z, float yaw, float pitch, float rad, vec &light) -{ - glPushMatrix(); - - glColor3fv((float *)&light); - - glTranslatef(x, y, z); - - glRotatef(yaw+180, 0, -1, 0); - glRotatef(pitch, 0, 0, 1); - - glRotatef(-90, 0, 1, 0); - glRotatef(-90, 1, 0, 0); - - glScalef( scale.x, scale.y, scale.z); - - render(); - - glPopMatrix(); -}; - -void md3setanim(dynent *d, int anim) -{ - if(anim <= BOTH_DEAD3) // assign to both, legs and torso - { - d->animstate[MDL_LOWER].anim = anim; - d->animstate[MDL_UPPER].anim = anim; - } - else if(anim <= TORSO_STAND2) - d->animstate[MDL_UPPER].anim = anim; - else - d->animstate[MDL_LOWER].anim = anim - 7; -}; - -vector models; -vector tmp_animations; -int firstweapon = -1; -int numskins = 0; -char basedir[_MAXDEFSTR]; // necessary for relative path's - -void md3skin(char *objname, char *skin) // called by the {lower|upper|head}.cfg (originally .skin in Q3A) -{ - md3model *mdl = models.last(); - loopv(mdl->meshes) - { - md3mesh *mesh = &mdl->meshes[i]; - if(strcmp(mesh->name, objname) == 0) - { - int xs, ys; - sprintf_sd(path)("%s/%s", basedir, skin); // 'skin' is a relative url - bool highqual = strstr(basedir, "weapons") != NULL; - installtex(FIRSTMD3 + numskins, path, xs, ys, true, highqual); - mesh->tex = numskins; - numskins++; - }; - }; -}; - -COMMAND(md3skin, ARG_2STR); - -void md3animation(char *first, char *nums, char *loopings, char *fps) /* configurable animations - use hardcoded instead ? */ -{ - md3animinfo &a = tmp_animations.add(); - a.start = atoi(first); - a.end = a.start + atoi(nums) - 1; - (atoi(loopings) > 0) ? a.loop = true : a.loop = false; - a.fps = atoi(fps); -}; - -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(); - loopi(NUMGUNS) - { - sprintf(basedir, "packages/models/weapons/%s", hudgunnames[i]); - md3model *mdl = new md3model(); - models.add(mdl); - sprintf_sd(mdl_path)("%s/tris_high.md3", basedir); - sprintf_sd(cfg_path)("%s/default.skin", basedir); - mdl->load(mdl_path); - exec(cfg_path); - loopj(NUM_MDL_GUN) mdl->animations.add(weaponsanim[j]); - } -}; - -void rotatevec(vec &point, vec &rot) -{ - vec tmp(point); - - point.x=tmp.z*sin(rot.x)+tmp.x*cos(rot.x); - point.y=tmp.y; - point.z=tmp.z*cos(rot.x)-tmp.x*sin(rot.x); - - point.x=tmp.x; - point.y=tmp.y*cos(rot.y)-tmp.z*sin(rot.y); - point.z=tmp.y*sin(rot.y)+tmp.z*cos(rot.y); - - point.x=tmp.y*sin(rot.z)+tmp.x*cos(rot.z); - point.y=tmp.y*cos(rot.z)-tmp.x*sin(rot.z); - point.z=tmp.z; -}; - -VAR(swayspeeddiv, 1, 200, 1000); -VAR(swaymovediv, 1, 10, 1000); - -void rendermd3gun() -{ - if(firstweapon >= 0) - { - int ix = (int)player1->o.x; - int iy = (int)player1->o.y; - vec light = { 1.0f, 1.0f, 1.0f }; - - if(!OUTBORD(ix, iy)) - { - sqr *s = S(ix,iy); - float ll = 256.0f; // 0.96f; - float of = 0.0f; // 0.1f; - 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); - if(player1->reloading) - { - weapon->animstate = new md3state(); - weapon->animstate->anim = 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) - { - int percent_done = (lastmillis-player1->lastaction)*100/attackdelay(player1->gunselect); - 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)); - }; - vdist(dist, unitv, player1->o, worldpos); - vdiv(unitv, dist); - float k_rot = kick_rot(player1->gunselect)*kick; - float k_back = kick_back(player1->gunselect)*kick/10; - - vec sway(unitv); - int swayt = (lastmillis+1)/10 % 100; - float swayspeed = (float) sin((float)lastmillis/swayspeeddiv)/swaymovediv; - - float plspeed = min(1.0f, 0.1f + sqrt(player1->vel.x*player1->vel.x + player1->vel.y*player1->vel.y)); - swayspeed *= plspeed/2; - - vec rot = {0.0f, 0.0f, 1.0f}; - - rotatevec(sway, rot); - - sway.x*=swayspeed; - sway.y*=swayspeed; - sway.z*=swayspeed; - - weapon->draw( player1->o.x-unitv.x*k_back+sway.x, - player1->o.z-unitv.z*k_back+sway.z, - player1->o.y-unitv.y*k_back+sway.y, - player1->yaw + 90, player1->pitch+k_rot, 1.0f, light); - }; - }; -}; - -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); - };*/ -}; - +// code for loading, linking and rendering md3 models + +// See http://www.draekko.org/documentation/md3format.html for informations about the md3 format + + + +#include "cube.h" + + + +const int FIRSTMD3 = 20; + +enum { MDL_LOWER = 0, MDL_UPPER, MDL_HEAD }; + + + +#define MD3_DEFAULT_SCALE 0.08f + + + +struct vec2 + +{ + + float x, y; + +}; + + + +struct md3tag + +{ + + char name[64]; + + float pos[3]; + + float rotation[3][3]; + +}; + + + +struct md3vertex + +{ + + signed short vertex[3]; + + uchar normal[2]; + +}; + + + +struct md3triangle + +{ + + int vertexindices[3]; + +}; + + + +struct md3animinfo + +{ + + int start; + + int end; + + bool loop; + + int fps; + +}; + + + +struct md3header + +{ + + char id[4]; + + int version; + + char name[64]; + + int flags; + + int numframes, numtags, nummeshes, numskins; + + int ofs_frames, ofs_tags, ofs_meshes, ofs_eof; // offsets + +}; + + + +struct md3meshheader + +{ + + char id[4]; + + char name[64]; + + int flags; + + int numframes, numshaders, numvertices, numtriangles; + + int ofs_triangles, ofs_shaders, ofs_uv, ofs_vertices, meshsize; // offsets + +}; + + + +struct md3mesh + +{ + + char name[64]; + + md3triangle *triangles; + + vec *vertices; + + vec2 *uv; + + int numtriangles, numvertices; // numvertices == numUV + + int tex; + +}; + + + +struct md3model + +{ + + vector meshes; + + vector animations; + + md3state *animstate; + + md3model **links; + + md3tag *tags; + + int numframes, numtags; + + bool loaded; + + vec scale; + + bool link(md3model *link, char *tag); + + bool load(char *path); + + void render(); + + void draw(float x, float y, float z, float yaw, float pitch, float rad, vec &light); + + md3model(); + + ~md3model(); + +}; + + + +md3model::md3model() + +{ + + loaded = false; + + scale.x = scale.y = scale.z = MD3_DEFAULT_SCALE; + + animstate = NULL; + +}; + + + +md3model::~md3model() + +{ + + loopv(meshes){ + + if(meshes[i].vertices) delete [] meshes[i].vertices; + + if(meshes[i].triangles) delete [] meshes[i].triangles; + + if(meshes[i].uv) delete [] meshes[i].uv; + + }; + + if(links) free(links); + + if(tags) delete [] tags; + +}; + + + +bool md3model::link(md3model *link, char *tag) + +{ + + loopi(numtags) + + if(strcmp(tags[i].name, tag) == 0) + + { + + links[i] = link; + + return true; + + }; + + return false; + +}; + + + +bool md3model::load(char *path) + +{ + + if(!path) return false; + + FILE *f = fopen(path, "rb"); + + if(!f) fatal("could not load md3 model: %s", path); + + md3header header; + + fread(&header, sizeof(md3header), 1, f); + + if(header.id[0] != 'I' || header.id[1] != 'D' || header.id[2] != 'P' || header.id[3] != '3' || header.version != 15) // header check + + fatal("corruped header in md3 model: %s", path); + + + + tags = new md3tag[header.numframes * header.numtags]; + + fseek(f, header.ofs_tags, SEEK_SET); + + fread(tags, sizeof(md3tag), header.numframes * header.numtags, f); + + numframes = header.numframes; + + numtags = header.numtags; + + + + links = (md3model **) malloc(sizeof(md3model) * header.numtags); + + loopi(header.numtags) links[i] = NULL; + + + + int mesh_offset = ftell(f); + + + + loopi(header.nummeshes) + + { + + md3mesh mesh; + + md3meshheader mheader; + + fseek(f, mesh_offset, SEEK_SET); + + fread(&mheader, sizeof(md3meshheader), 1, f); + + strn0cpy(mesh.name, mheader.name, 64); + + + + mesh.triangles = new md3triangle[mheader.numtriangles]; + + fseek(f, mesh_offset + mheader.ofs_triangles, SEEK_SET); + + fread(mesh.triangles, sizeof(md3triangle), mheader.numtriangles, f); // read the triangles + + mesh.numtriangles = mheader.numtriangles; + + + + mesh.uv = new vec2[mheader.numvertices]; + + fseek(f, mesh_offset + mheader.ofs_uv , SEEK_SET); + + fread(mesh.uv, sizeof(vec2), mheader.numvertices, f); // read the UV data + + + + md3vertex *vertices = new md3vertex[mheader.numframes * mheader.numvertices]; + + fseek(f, mesh_offset + mheader.ofs_vertices, SEEK_SET); + + fread(vertices, sizeof(md3vertex), mheader.numframes * mheader.numvertices, f); // read the vertices + + mesh.vertices = new vec[mheader.numframes * mheader.numvertices]; // transform to our own structure + + loopj(mheader.numframes * mheader.numvertices) + + { + + mesh.vertices[j].x = vertices[j].vertex[0] / 64.0f; + + mesh.vertices[j].y = vertices[j].vertex[1] / 64.0f; + + mesh.vertices[j].z = vertices[j].vertex[2] / 64.0f; + + }; + + mesh.numvertices = mheader.numvertices; + + + + meshes.add(mesh); + + mesh_offset += mheader.meshsize; + + delete [] vertices; + + }; + + + + loaded = true; + + return true; + +}; + + + +VAR(anim, 0, 0, 100); + + + +void md3model::render() + +{ + + if(!loaded || meshes.length() <= 0) return; + + float t = 0.0f; + + int nextfrm; + + 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 + + if(t >= 1.0f) // jump to the next keyframe + + { + + a->frm++; + + a->lastTime = lastmillis; + + t = 0.0f; + + }; + + if(a->frm >= info->end) + + { + + if(info->loop) + + a->frm = info->start; + + else + + { + + a->frm = info->end; + + t = 0.0f; // stops the animation + + }; + + }; + + } + + else + + { + + a = new md3state(); + + a->frm = 0; + + } + + a->frm = anim; //fixme + + nextfrm = a->frm + 1; + + + + #define interpolate(p1,p2) ((p1) + t * ((p2) - (p1))) + + + + loopv(meshes) + + { + + md3mesh *mesh = &meshes[i]; + + + + glBindTexture(GL_TEXTURE_2D, FIRSTMD3 + mesh->tex); + + + + glBegin(GL_TRIANGLES); + + loopj(mesh->numtriangles) // triangles + + { + + loopk(3) // vertices + + { + + int index = mesh->triangles[j].vertexindices[k]; + + vec *point1 = &mesh->vertices[index + a->frm * mesh->numvertices]; + + vec *point2 = &mesh->vertices[index + nextfrm * mesh->numvertices]; + + + + if(mesh->uv) + + glTexCoord2f(mesh->uv[index].x, mesh->uv[index].y); + + + + glVertex3f( interpolate(point1->x, point2->x), + + interpolate(point1->y, point2->y), + + interpolate(point1->z, point2->z)); + + }; + + }; + + glEnd(); + + }; + + + + loopi(numtags) // render the linked models - interpolate rotation and position of the 'link-tags' + + { + + md3model *link = links[i]; + + if(!link) continue; + + GLfloat matrix[16] = {0}; // fixme: un-obfuscate it! + + loopj(3) matrix[j] = interpolate(tags[a->frm * numtags + i].rotation[0][j], tags[nextfrm * numtags + i].rotation[0][j]); // rotation + + loopj(3) matrix[4 + j] = interpolate(tags[a->frm * numtags + i].rotation[1][j], tags[nextfrm * numtags + i].rotation[1][j]); + + loopj(3) matrix[8 + j] = interpolate(tags[a->frm * numtags + i].rotation[2][j], tags[nextfrm * numtags + i].rotation[2][j]); + + loopj(3) matrix[12 + j] = interpolate(tags[a->frm * numtags + i].pos[j] , tags[nextfrm * numtags + i].pos[j]); // position + + matrix[15] = 1.0f; + + glPushMatrix(); + + glMultMatrixf(matrix); + + link->render(); + + glPopMatrix(); + + }; + +}; + + + +void md3model::draw(float x, float y, float z, float yaw, float pitch, float rad, vec &light) + +{ + + glPushMatrix(); + + + + glColor3fv((float *)&light); + + + + glTranslatef(x, y, z); + + + + glRotatef(yaw+180, 0, -1, 0); + + glRotatef(pitch, 0, 0, 1); + + + + glRotatef(-90, 0, 1, 0); + + glRotatef(-90, 1, 0, 0); + + + + glScalef( scale.x, scale.y, scale.z); + + + + render(); + + + + glPopMatrix(); + +}; + + + +void md3setanim(dynent *d, int anim) + +{ + + if(anim <= BOTH_DEAD3) // assign to both, legs and torso + + { + + d->animstate[MDL_LOWER].anim = anim; + + d->animstate[MDL_UPPER].anim = anim; + + } + + else if(anim <= TORSO_STAND2) + + d->animstate[MDL_UPPER].anim = anim; + + else + + d->animstate[MDL_LOWER].anim = anim - 7; + +}; + + + +vector models; + +vector tmp_animations; + +int firstweapon = -1; + +int numskins = 0; + +char basedir[_MAXDEFSTR]; // necessary for relative path's + + + +void md3skin(char *objname, char *skin) // called by the {lower|upper|head}.cfg (originally .skin in Q3A) + +{ + + md3model *mdl = models.last(); + + loopv(mdl->meshes) + + { + + md3mesh *mesh = &mdl->meshes[i]; + + if(strcmp(mesh->name, objname) == 0) + + { + + int xs, ys; + + sprintf_sd(path)("%s/%s", basedir, skin); // 'skin' is a relative url + + bool highqual = strstr(basedir, "weapons") != NULL; + + installtex(FIRSTMD3 + numskins, path, xs, ys, true, highqual); + + mesh->tex = numskins; + + numskins++; + + }; + + }; + +}; + + + +COMMAND(md3skin, ARG_2STR); + + + +void md3animation(char *first, char *nums, char *loopings, char *fps) /* configurable animations - use hardcoded instead ? */ + +{ + + md3animinfo &a = tmp_animations.add(); + + a.start = atoi(first); + + a.end = a.start + atoi(nums) - 1; + + (atoi(loopings) > 0) ? a.loop = true : a.loop = false; + + a.fps = atoi(fps); + +}; + + + +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(); + + loopi(NUMGUNS) + + { + + sprintf(basedir, "packages/models/weapons/%s", hudgunnames[i]); + + md3model *mdl = new md3model(); + + models.add(mdl); + + sprintf_sd(mdl_path)("%s/tris_high.md3", basedir); + + sprintf_sd(cfg_path)("%s/default.skin", basedir); + + mdl->load(mdl_path); + + exec(cfg_path); + + loopj(NUM_MDL_GUN) mdl->animations.add(weaponsanim[j]); + + } + +}; + + + +void rotatevec(vec &point, vec &rot) + +{ + + vec tmp(point); + + + + point.x=tmp.z*sin(rot.x)+tmp.x*cos(rot.x); + + point.y=tmp.y; + + point.z=tmp.z*cos(rot.x)-tmp.x*sin(rot.x); + + + + point.x=tmp.x; + + point.y=tmp.y*cos(rot.y)-tmp.z*sin(rot.y); + + point.z=tmp.y*sin(rot.y)+tmp.z*cos(rot.y); + + + + point.x=tmp.y*sin(rot.z)+tmp.x*cos(rot.z); + + point.y=tmp.y*cos(rot.z)-tmp.x*sin(rot.z); + + point.z=tmp.z; + +}; + + + +VAR(swayspeeddiv, 1, 200, 1000); + +VAR(swaymovediv, 1, 10, 1000); + + + +void rendermd3gun() + +{ + + if(firstweapon >= 0) + + { + + int ix = (int)player1->o.x; + + int iy = (int)player1->o.y; + + vec light = { 1.0f, 1.0f, 1.0f }; + + + + if(!OUTBORD(ix, iy)) + + { + + sqr *s = S(ix,iy); + + float ll = 256.0f; // 0.96f; + + float of = 0.0f; // 0.1f; + + 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); + + if(player1->reloading) + + { + + weapon->animstate = new md3state(); + + weapon->animstate->anim = 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) + + { + + int percent_done = (lastmillis-player1->lastaction)*100/attackdelay(player1->gunselect); + + 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)); + + }; + + vdist(dist, unitv, player1->o, worldpos); + + vdiv(unitv, dist); + + float k_rot = kick_rot(player1->gunselect)*kick; + + float k_back = kick_back(player1->gunselect)*kick/10; + + + + vec sway(unitv); + + int swayt = (lastmillis+1)/10 % 100; + + float swayspeed = (float) sin((float)lastmillis/swayspeeddiv)/swaymovediv; + + + + float plspeed = min(1.0f, 0.1f + sqrt(player1->vel.x*player1->vel.x + player1->vel.y*player1->vel.y)); + + swayspeed *= plspeed/2; + + + + vec rot = {0.0f, 0.0f, 1.0f}; + + + + rotatevec(sway, rot); + + + + sway.x*=swayspeed; + + sway.y*=swayspeed; + + sway.z*=swayspeed; + + + + weapon->draw( player1->o.x-unitv.x*k_back+sway.x, + + player1->o.z-unitv.z*k_back+sway.z, + + player1->o.y-unitv.y*k_back+sway.y, + + player1->yaw + 90, player1->pitch+k_rot, 1.0f, light); + + }; + + }; + +}; + + + +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); + + };*/ + +}; + + +