OctaCore/src/game/client.cc

632 lines
20 KiB
C++

#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<uchar> 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(fx<r && fy<r && fz<rz && player1->state!=CS_SPECTATOR && d->state!=CS_DEAD)
{
if(fx<fy) d->o.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<<k)) { n |= p.get()<<16; if(n&0x800000) n |= ~0U<<24; }
o[k] = n/DMF;
}
int dir = p.get(); dir |= p.get()<<8;
yaw = dir%360;
pitch = clamp(dir/360, 0, 180)-90;
roll = clamp(int(p.get()), 0, 180)-90;
int mag = p.get(); if(flags&(1<<3)) mag |= p.get()<<8;
dir = p.get(); dir |= p.get()<<8;
vecfromyawpitch(dir%360, clamp(dir/360, 0, 180)-90, 1, 0, vel);
vel.mul(mag/DVELF);
if(flags&(1<<4))
{
mag = p.get(); if(flags&(1<<5)) mag |= p.get()<<8;
if(flags&(1<<6))
{
dir = p.get(); dir |= p.get()<<8;
vecfromyawpitch(dir%360, clamp(dir/360, 0, 180)-90, 1, 0, falling);
}
else falling = vec(0, 0, -1);
falling.mul(mag/DVELF);
}
else falling = vec(0, 0, 0);
int seqcolor = (physstate>>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<<newsize < getworldsize()) newsize++;
conoutf(size>=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;
}
}
}