#include "game.hh" namespace game { VARP(minradarscale, 0, 384, 10000); VARP(maxradarscale, 1, 1024, 10000); VARP(radarteammates, 0, 1, 1); FVARP(minimapalpha, 0, 1, 1); float calcradarscale() { return clamp(max(minimapradius.x, minimapradius.y)/3, float(minradarscale), float(maxradarscale)); } void drawminimap(gameent *d, float x, float y, float s) { vec pos = vec(d->o).sub(minimapcenter).mul(minimapscale).add(0.5f), dir; vecfromyawpitch(camera1->yaw, 0, 1, 0, dir); float scale = calcradarscale(); gle::defvertex(2); gle::deftexcoord0(); gle::begin(GL_TRIANGLE_FAN); loopi(16) { vec v = vec(0, -1, 0).rotate_around_z(i/16.0f*2*M_PI); gle::attribf(x + 0.5f*s*(1.0f + v.x), y + 0.5f*s*(1.0f + v.y)); vec tc = vec(dir).rotate_around_z(i/16.0f*2*M_PI); gle::attribf(1.0f - (pos.x + tc.x*scale*minimapscale.x), pos.y + tc.y*scale*minimapscale.y); } gle::end(); } void setradartex() { settexture("media/interface/radar/radar.png", 3); } clientmode *cmode = NULL; void setclientmode() { cmode = NULL; } int lastping = 0; bool connected = false, remote = false; int sessionid = 0; string servdesc = "", servauth = ""; VARP(deadpush, 1, 2, 20); void sendmapinfo() { } void writeclientinfo(stream *f) { f->printf("name %s\n", escapestring(player1->name)); } bool allowedittoggle() { if(editmode) return true; if(isconnected() && multiplayer(false) && !m_edit) { conoutf(CON_ERROR, "editing in multiplayer requires edit mode"); return false; } return execidentbool("allowedittoggle", true); } void edittoggled(bool on) { if(player1->state==CS_DEAD) deathstate(player1, true); disablezoom(); player1->respawned = -2; checkfollow(); } string clientmap = ""; void changemapserv(const char *name, int mode) // forced map change from the server { if(editmode) toggleedit(); if((m_edit && !name[0]) || !load_world(name)) { emptymap(0, true, name); } startgame(); } void changemap(const char *name, int mode) // request map change, server may ignore { server::forcemap(name, mode); if(!isconnected()) localconnect(); } void changemap(const char *name) { changemap(name, 0); } ICOMMAND(map, "s", (char *name), changemap(name)); void forceedit(const char *name) { changemap(name, 0); } void newmap(int size) { addmsg(N_NEWMAP, "ri", size); } void edittrigger(const selinfo &sel, int op, int arg1, int arg2, int arg3, const VSlot *vs) { } void vartrigger(ident *id) { } bool ispaused() { return false; } bool allowmouselook() { return true; } int scaletime(int t) { return t*100; } // collect c2s messages conveniently vector messages; int messagecn = -1, messagereliable = false; bool addmsg(int type, const char *fmt, ...) { if(!connected) return false; static uchar buf[MAXTRANS]; ucharbuf p(buf, sizeof(buf)); putint(p, type); int numi = 1, numf = 0, nums = 0; bool reliable = false; if(fmt) { va_list args; va_start(args, fmt); while(*fmt) switch(*fmt++) { case 'r': reliable = true; break; case 'c': { break; } case 'v': { int n = va_arg(args, int); int *v = va_arg(args, int *); loopi(n) putint(p, v[i]); numi += n; break; } case 'i': { int n = isdigit(*fmt) ? *fmt++-'0' : 1; loopi(n) putint(p, va_arg(args, int)); numi += n; break; } case 'f': { int n = isdigit(*fmt) ? *fmt++-'0' : 1; loopi(n) putfloat(p, (float)va_arg(args, double)); numf += n; break; } case 's': sendstring(va_arg(args, const char *), p); nums++; break; } va_end(args); } int num = nums || numf ? 0 : numi, msgsize = server::msgsizelookup(type); if(msgsize && num!=msgsize) { fatal("inconsistent msg size for %d (%d != %d)", type, num, msgsize); } if(reliable) messagereliable = true; messages.put(buf, p.length()); return true; } void connectattempt(const char *name, const char *password, const ENetAddress &address) { } void connectfail() { } void gameconnect(bool _remote) { remote = _remote; } void gamedisconnect(bool cleanup) { if(remote) stopfollowing(); connected = remote = false; player1->clientnum = -1; if(editmode) toggleedit(); sessionid = 0; messages.setsize(0); messagereliable = false; messagecn = -1; player1->respawn(); player1->lifesequence = 0; player1->state = CS_ALIVE; clearclients(false); if(cleanup) { clientmap[0] = '\0'; } } const char *chatcolorname(gameent *d) { return colorname(d); } void toserver(char *text) { conoutf(CON_CHAT, "%s", text); } static void sendposition(gameent *d, packetbuf &q) { putint(q, N_POS); putuint(q, d->clientnum); // 3 bits phys state, 1 bit life sequence, 2 bits move, 2 bits strafe uchar physstate = d->physstate | ((d->lifesequence&1)<<3) | ((d->move&3)<<4) | ((d->strafe&3)<<6); q.put(physstate); ivec o = ivec(vec(d->o.x, d->o.y, d->o.z-d->eyeheight).mul(DMF)); uint vel = min(int(d->vel.magnitude()*DVELF), 0xFFFF), fall = min(int(d->falling.magnitude()*DVELF), 0xFFFF); // 3 bits position, 1 bit velocity, 3 bits falling, 1 bit material, 1 bit crouching uint flags = 0; if(o.x < 0 || o.x > 0xFFFF) flags |= 1<<0; if(o.y < 0 || o.y > 0xFFFF) flags |= 1<<1; if(o.z < 0 || o.z > 0xFFFF) flags |= 1<<2; if(vel > 0xFF) flags |= 1<<3; if(fall > 0) { flags |= 1<<4; if(fall > 0xFF) flags |= 1<<5; if(d->falling.x || d->falling.y || d->falling.z > 0) flags |= 1<<6; } if((lookupmaterial(d->feetpos())&MATF_CLIP) == MAT_GAMECLIP) flags |= 1<<7; if(d->crouching < 0) flags |= 1<<8; putuint(q, flags); loopk(3) { q.put(o[k]&0xFF); q.put((o[k]>>8)&0xFF); if(o[k] < 0 || o[k] > 0xFFFF) q.put((o[k]>>16)&0xFF); } uint dir = (d->yaw < 0 ? 360 + int(d->yaw)%360 : int(d->yaw)%360) + clamp(int(d->pitch+90), 0, 180)*360; q.put(dir&0xFF); q.put((dir>>8)&0xFF); q.put(clamp(int(d->roll+90), 0, 180)); q.put(vel&0xFF); if(vel > 0xFF) q.put((vel>>8)&0xFF); float velyaw, velpitch; vectoyawpitch(d->vel, velyaw, velpitch); uint veldir = (velyaw < 0 ? 360 + int(velyaw)%360 : int(velyaw)%360) + clamp(int(velpitch+90), 0, 180)*360; q.put(veldir&0xFF); q.put((veldir>>8)&0xFF); if(fall > 0) { q.put(fall&0xFF); if(fall > 0xFF) q.put((fall>>8)&0xFF); if(d->falling.x || d->falling.y || d->falling.z > 0) { float fallyaw, fallpitch; vectoyawpitch(d->falling, fallyaw, fallpitch); uint falldir = (fallyaw < 0 ? 360 + int(fallyaw)%360 : int(fallyaw)%360) + clamp(int(fallpitch+90), 0, 180)*360; q.put(falldir&0xFF); q.put((falldir>>8)&0xFF); } } } void sendposition(gameent *d, bool reliable) { if(d->state != CS_ALIVE && d->state != CS_EDITING) return; packetbuf q(100, reliable ? ENET_PACKET_FLAG_RELIABLE : 0); sendposition(d, q); sendclientpacket(q.finalize(), 0); } void sendpositions() { loopv(players) { gameent *d = players[i]; if((d == player1) && (d->state == CS_ALIVE || d->state == CS_EDITING)) { packetbuf q(100); sendposition(d, q); for(int j = i+1; j < players.length(); j++) { gameent *d = players[j]; if((d == player1) && (d->state == CS_ALIVE || d->state == CS_EDITING)) sendposition(d, q); } sendclientpacket(q.finalize(), 0); break; } } } void sendmessages() { packetbuf p(MAXTRANS); if(messages.length()) { p.put(messages.getbuf(), messages.length()); messages.setsize(0); if(messagereliable) p.reliable(); messagereliable = false; messagecn = -1; } if(totalmillis-lastping>250) { putint(p, N_PING); putint(p, totalmillis); lastping = totalmillis; } sendclientpacket(p.finalize(), 1); } void c2sinfo(bool force) // send update to the server { static int lastupdate = -1000; if(totalmillis - lastupdate < 40 && !force) return; // don't update faster than 30fps lastupdate = totalmillis; sendpositions(); sendmessages(); flushclient(); } void sendintro() { packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); putint(p, N_CONNECT); sendstring(player1->name, p); sendclientpacket(p.finalize(), 1); } void updatepos(gameent *d) { // update the position of other clients in the game in our world // don't care if he's in the scenery or other players, // just don't overlap with our client const float r = player1->radius+d->radius; const float dx = player1->o.x-d->o.x; const float dy = player1->o.y-d->o.y; const float dz = player1->o.z-d->o.z; const float rz = player1->aboveeye+d->eyeheight; const float fx = (float)fabs(dx), fy = (float)fabs(dy), fz = (float)fabs(dz); if(fxstate!=CS_SPECTATOR && d->state!=CS_DEAD) { if(fxo.y += dy<0 ? r-fy : -(r-fy); // push aside else d->o.x += dx<0 ? r-fx : -(r-fx); } int lagtime = totalmillis-d->lastupdate; if(lagtime) { if(d->state!=CS_SPAWNING && d->lastupdate) d->plag = (d->plag*5+lagtime)/6; d->lastupdate = totalmillis; } } void parsepositions(ucharbuf &p) { int type; while(p.remaining()) switch(type = getint(p)) { case N_POS: // position of another client { int cn = getuint(p), physstate = p.get(), flags = getuint(p); vec o, vel, falling; float yaw, pitch, roll; loopk(3) { int n = p.get(); n |= p.get()<<8; if(flags&(1<>3)&1; gameent *d = getclient(cn); if(!d || d->lifesequence < 0 || seqcolor!=(d->lifesequence&1) || d->state==CS_DEAD) continue; float oldyaw = d->yaw, oldpitch = d->pitch, oldroll = d->roll; d->yaw = yaw; d->pitch = pitch; d->roll = roll; d->move = (physstate>>4)&2 ? -1 : (physstate>>4)&1; d->strafe = (physstate>>6)&2 ? -1 : (physstate>>6)&1; d->crouching = (flags&(1<<8))!=0 ? -1 : abs(d->crouching); vec oldpos(d->o); d->o = o; d->o.z += d->eyeheight; d->vel = vel; d->falling = falling; d->physstate = physstate&7; updatephysstate(d); updatepos(d); if(smoothmove && d->smoothmillis>=0 && oldpos.dist(d->o) < smoothdist) { d->newpos = d->o; d->newyaw = d->yaw; d->newpitch = d->pitch; d->newroll = d->roll; d->o = oldpos; d->yaw = oldyaw; d->pitch = oldpitch; d->roll = oldroll; (d->deltapos = oldpos).sub(d->newpos); d->deltayaw = oldyaw - d->newyaw; if(d->deltayaw > 180) d->deltayaw -= 360; else if(d->deltayaw < -180) d->deltayaw += 360; d->deltapitch = oldpitch - d->newpitch; d->deltaroll = oldroll - d->newroll; d->smoothmillis = lastmillis; } else d->smoothmillis = 0; if(d->state==CS_LAGGED || d->state==CS_SPAWNING) d->state = CS_ALIVE; break; } default: neterr("type"); return; } } void parsestate(gameent *d, ucharbuf &p, bool resume = false) { if(!d) { static gameent dummy; d = &dummy; } if(resume) { if(d==player1) getint(p); else d->state = getint(p); } d->lifesequence = getint(p); } void parsemessages(int cn, gameent *d, ucharbuf &p) { static char text[MAXTRANS]; int type; while(p.remaining()) switch(type = getint(p)) { case N_SERVINFO: // welcome messsage from the server { int mycn = getint(p), prot = getint(p); if(prot!=PROTOCOL_VERSION) { conoutf(CON_ERROR, "you are using a different game protocol (you: %d, server: %d)", PROTOCOL_VERSION, prot); disconnect(); return; } sessionid = getint(p); player1->clientnum = mycn; // we are now connected sendintro(); break; } case N_WELCOME: { connected = true; notifywelcome(); break; } case N_CLIENT: { int cn = getint(p), len = getuint(p); ucharbuf q = p.subbuf(len); parsemessages(cn, getclient(cn), q); break; } case N_MAPCHANGE: getstring(text, p); changemapserv(text, 0); break; case N_INITCLIENT: // another client either connected or changed name/team { int cn = getint(p); gameent *d = newclient(cn); if(!d) { getstring(text, p); getstring(text, p); getint(p); getint(p); break; } getstring(text, p); filtertext(text, text, false, false, MAXNAMELEN); if(!text[0]) copystring(text, "unnamed"); copystring(d->name, text, MAXNAMELEN+1); d->playermodel = 0; d->playercolor = 0; break; } case N_CDIS: clientdisconnected(getint(p)); break; case N_SPAWN: { if(d) { d->respawn(); } parsestate(d, p); if(!d) break; d->state = CS_SPAWNING; if(d == followingplayer()) lasthit = 0; checkfollow(); break; } case N_SPAWNSTATE: { int scn = getint(p); gameent *s = getclient(scn); if(!s) { parsestate(NULL, p); break; } if(s==player1) { if(editmode) toggleedit(); } s->respawn(); parsestate(s, p); s->state = CS_ALIVE; if(cmode) cmode->pickspawn(s); else findplayerspawn(s, -1, 0); if(s == player1) { lasthit = 0; } if(cmode) cmode->respawned(s); checkfollow(); addmsg(N_SPAWN, "rci", s, s->lifesequence); break; } case N_RESUME: { for(;;) { int cn = getint(p); if(p.overread() || cn<0) break; gameent *d = (cn == player1->clientnum ? player1 : newclient(cn)); parsestate(d, p, true); } break; } case N_PONG: addmsg(N_CLIENTPING, "i", player1->ping = (player1->ping*5+totalmillis-getint(p))/6); break; case N_CLIENTPING: if(!d) return; d->ping = getint(p); break; case N_NEWMAP: { int size = getint(p); if(size>=0) emptymap(size, true, NULL); else enlargemap(true); if(d && d!=player1) { int newsize = 0; while(1<=0 ? "%s started a new map of size %d" : "%s enlarged the map to size %d", colorname(d), newsize); } break; } default: neterr("type", cn < 0); return; } } void receivefile(packetbuf &p) { } void parsepacketclient(int chan, packetbuf &p) // processes any updates from the server { if(p.packet->flags&ENET_PACKET_FLAG_UNSEQUENCED) return; switch(chan) { case 0: parsepositions(p); break; case 1: parsemessages(-1, NULL, p); break; case 2: receivefile(p); break; } } }