#include "qtv.h" typedef struct { unsigned char msec; unsigned short angles[3]; short forwardmove, sidemove, upmove; unsigned char buttons; unsigned char impulse; } usercmd_t; const usercmd_t nullcmd; #define CM_ANGLE1 (1<<0) #define CM_ANGLE3 (1<<1) #define CM_FORWARD (1<<2) #define CM_SIDE (1<<3) #define CM_UP (1<<4) #define CM_BUTTONS (1<<5) #define CM_IMPULSE (1<<6) #define CM_ANGLE2 (1<<7) void ReadDeltaUsercmd (netmsg_t *m, const usercmd_t *from, usercmd_t *move) { int bits; memcpy (move, from, sizeof(*move)); bits = ReadByte (m); // read current angles if (bits & CM_ANGLE1) move->angles[0] = ReadShort (m); if (bits & CM_ANGLE2) move->angles[1] = ReadShort (m); if (bits & CM_ANGLE3) move->angles[2] = ReadShort (m); // read movement if (bits & CM_FORWARD) move->forwardmove = ReadShort(m); if (bits & CM_SIDE) move->sidemove = ReadShort(m); if (bits & CM_UP) move->upmove = ReadShort(m); // read buttons if (bits & CM_BUTTONS) move->buttons = ReadByte (m); if (bits & CM_IMPULSE) move->impulse = ReadByte (m); // read time to run command move->msec = ReadByte (m); // always sent } void WriteDeltaUsercmd (netmsg_t *m, const usercmd_t *from, usercmd_t *move) { int bits = 0; if (move->angles[0] != from->angles[0]) bits |= CM_ANGLE1; if (move->angles[1] != from->angles[1]) bits |= CM_ANGLE2; if (move->angles[2] != from->angles[2]) bits |= CM_ANGLE3; if (move->forwardmove != from->forwardmove) bits |= CM_FORWARD; if (move->sidemove != from->sidemove) bits |= CM_SIDE; if (move->upmove != from->upmove) bits |= CM_UP; if (move->buttons != from->buttons) bits |= CM_BUTTONS; if (move->impulse != from->impulse) bits |= CM_IMPULSE; WriteByte (m, bits); // read current angles if (bits & CM_ANGLE1) WriteShort (m, move->angles[0]); if (bits & CM_ANGLE2) WriteShort (m, move->angles[1]); if (bits & CM_ANGLE3) WriteShort (m, move->angles[2]); // read movement if (bits & CM_FORWARD) WriteShort(m, move->forwardmove); if (bits & CM_SIDE) WriteShort(m, move->sidemove); if (bits & CM_UP) WriteShort(m, move->upmove); // read buttons if (bits & CM_BUTTONS) WriteByte (m, move->buttons); if (bits & CM_IMPULSE) WriteByte (m, move->impulse); // read time to run command WriteByte (m, move->msec); // always sent } SOCKET QW_InitUDPSocket(int port) { int sock; struct sockaddr_in address; // int fromlen; unsigned long nonblocking = true; address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons((u_short)port); if ((sock = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) { return INVALID_SOCKET; } if (ioctlsocket (sock, FIONBIO, &nonblocking) == -1) { closesocket(sock); return INVALID_SOCKET; } if( bind (sock, (void *)&address, sizeof(address)) == -1) { closesocket(sock); return INVALID_SOCKET; } return sock; } void BuildServerData(sv_t *tv, netmsg_t *msg, qboolean mvd) { WriteByte(msg, svc_serverdata); WriteLong(msg, PROTOCOL_VERSION); WriteLong(msg, tv->servercount); WriteString(msg, tv->gamedir); if (mvd) WriteFloat(msg, 0); else WriteByte(msg, MAX_CLIENTS-1); WriteString(msg, tv->mapname); // get the movevars WriteFloat(msg, tv->movevars.gravity); WriteFloat(msg, tv->movevars.stopspeed); WriteFloat(msg, tv->movevars.maxspeed); WriteFloat(msg, tv->movevars.spectatormaxspeed); WriteFloat(msg, tv->movevars.accelerate); WriteFloat(msg, tv->movevars.airaccelerate); WriteFloat(msg, tv->movevars.wateraccelerate); WriteFloat(msg, tv->movevars.friction); WriteFloat(msg, tv->movevars.waterfriction); WriteFloat(msg, tv->movevars.entgrav); WriteByte(msg, svc_stufftext); WriteString2(msg, "fullserverinfo "); WriteString2(msg, "\\*qtv\\" VERSION); WriteString2(msg, tv->serverinfo); WriteString(msg, "\n"); } void SendServerData(sv_t *tv, viewer_t *viewer) { netmsg_t msg; char buffer[1024]; InitNetMsg(&msg, buffer, sizeof(buffer)); BuildServerData(tv, &msg, false); SendBufferToViewer(viewer, msg.data, msg.cursize, true); viewer->thinksitsconnected = false; memset(viewer->currentstats, 0, sizeof(viewer->currentstats)); } int SendCurrentUserinfos(sv_t *tv, int cursize, netmsg_t *msg, int i) { if (i < 0) return i; if (i >= MAX_CLIENTS) return i; for (; i < MAX_CLIENTS-1; i++) { if (msg->cursize+cursize+strlen(tv->players[i].userinfo) > 768) { return i; } WriteByte(msg, svc_updateuserinfo); WriteByte(msg, i); WriteLong(msg, i); WriteString(msg, tv->players[i].userinfo); } WriteByte(msg, svc_updateuserinfo); WriteByte(msg, MAX_CLIENTS-1); WriteLong(msg, MAX_CLIENTS-1); WriteString(msg, "\\*spectator\\1\\name\\YOU!"); i++; return i; } int SendCurrentBaselines(sv_t *tv, int cursize, netmsg_t *msg, int i) { int j; if (i < 0 || i >= MAX_ENTITIES) return i; for (; i < MAX_ENTITIES; i++) { if (msg->cursize+cursize+16 > 768) { return i; } WriteByte(msg, svc_spawnbaseline); WriteShort(msg, i); WriteByte(msg, tv->baseline[i].modelindex); WriteByte(msg, tv->baseline[i].frame); WriteByte(msg, tv->baseline[i].colormap); WriteByte(msg, tv->baseline[i].skinnum); for (j = 0; j < 3; j++) { WriteShort(msg, tv->baseline[i].origin[j]); WriteByte(msg, tv->baseline[i].angles[j]); } } return i; } int SendCurrentLightmaps(sv_t *tv, int cursize, netmsg_t *msg, int i) { if (i < 0 || i >= MAX_LIGHTSTYLES) return i; for (; i < MAX_LIGHTSTYLES; i++) { if (msg->cursize+cursize+strlen(tv->lightstyle[i].name) > 768) { return i; } WriteByte(msg, svc_lightstyle); WriteByte(msg, i); WriteString(msg, tv->lightstyle[i].name); } return i; } int SendList(sv_t *qtv, int first, filename_t *list, int svc, netmsg_t *msg) { int i; WriteByte(msg, svc); WriteByte(msg, first); for (i = first+1; i < 256; i++) { printf("write %i: %s\n", i, list[i].name); WriteString(msg, list[i].name); if (!*list[i].name) //fixme: this probably needs testing for where we are close to the limit { //no more WriteByte(msg, 0); return -1; } if (msg->cursize > 768) { //truncate i--; break; } } WriteByte(msg, 0); WriteByte(msg, i); return i; } void NewQWClient(sv_t *qtv, netadr_t *addr, int qport) { viewer_t *viewer; viewer = malloc(sizeof(viewer_t)); memset(viewer, 0, sizeof(viewer_t)); viewer->trackplayer = -1; Netchan_Setup (qtv->qwdsocket, &viewer->netchan, *addr, qport); viewer->next = qtv->viewers; qtv->viewers = viewer; viewer->delta_frame = -1; Netchan_OutOfBandPrint(qtv->qwdsocket, *addr, "j"); } //fixme: will these want to have state?.. int NewChallenge(netadr_t *addr) { return 4; } qboolean ChallengePasses(netadr_t *addr, int challenge) { if (challenge == 4) return true; return false; } void ConnectionlessPacket(sv_t *qtv, netadr_t *from, netmsg_t *m) { char buffer[1024]; int i; ReadLong(m); ReadString(m, buffer, sizeof(buffer)); if (!strncmp(buffer, "getchallenge", 12)) { i = NewChallenge(from); Netchan_OutOfBandPrint(qtv->qwdsocket, *from, "c%i", i); return; } if (!strncmp(buffer, "connect 28 ", 11)) { NewQWClient(qtv, from, atoi(buffer+11)); return; } } void SV_WriteDelta(int entnum, const entity_state_t *from, const entity_state_t *to, netmsg_t *msg, qboolean force) { unsigned int i; unsigned int bits; bits = 0; if (from->angles[0] != to->angles[0]) bits |= U_ANGLE1; if (from->angles[1] != to->angles[1]) bits |= U_ANGLE2; if (from->angles[2] != to->angles[2]) bits |= U_ANGLE3; if (from->origin[0] != to->origin[0]) bits |= U_ORIGIN1; if (from->origin[1] != to->origin[1]) bits |= U_ORIGIN2; if (from->origin[2] != to->origin[2]) bits |= U_ORIGIN3; if (from->colormap != to->colormap) bits |= U_COLORMAP; if (from->skinnum != to->skinnum) bits |= U_SKIN; if (from->modelindex != to->modelindex) bits |= U_MODEL; if (from->frame != to->frame) bits |= U_FRAME; if (from->effects != to->effects) bits |= U_EFFECTS; if (bits & 255) bits |= U_MOREBITS; if (!bits && !force) return; i = (entnum&511) | (bits&~511); WriteShort (msg, i); if (bits & U_MOREBITS) WriteByte (msg, bits&255); /* #ifdef PROTOCOLEXTENSIONS if (bits & U_EVENMORE) WriteByte (msg, evenmorebits&255); if (evenmorebits & U_YETMORE) WriteByte (msg, (evenmorebits>>8)&255); #endif */ if (bits & U_MODEL) WriteByte (msg, to->modelindex&255); if (bits & U_FRAME) WriteByte (msg, to->frame); if (bits & U_COLORMAP) WriteByte (msg, to->colormap); if (bits & U_SKIN) WriteByte (msg, to->skinnum); if (bits & U_EFFECTS) WriteByte (msg, to->effects&0x00ff); if (bits & U_ORIGIN1) WriteShort (msg, to->origin[0]); if (bits & U_ANGLE1) WriteByte(msg, to->angles[0]); if (bits & U_ORIGIN2) WriteShort (msg, to->origin[1]); if (bits & U_ANGLE2) WriteByte(msg, to->angles[1]); if (bits & U_ORIGIN3) WriteShort (msg, to->origin[2]); if (bits & U_ANGLE3) WriteByte(msg, to->angles[2]); } const entity_state_t nullentstate; void SV_EmitPacketEntities (const sv_t *qtv, const viewer_t *v, const packet_entities_t *to, netmsg_t *msg) { const entity_state_t *baseline; const packet_entities_t *from; int oldindex, newindex; int oldnum, newnum; int oldmax; // this is the frame that we are going to delta update from if (v->delta_frame != -1) { from = &v->frame[v->delta_frame & (ENTITY_FRAMES-1)]; oldmax = from->numents; WriteByte (msg, svc_deltapacketentities); WriteByte (msg, v->delta_frame); } else { oldmax = 0; // no delta update from = NULL; WriteByte (msg, svc_packetentities); } newindex = 0; oldindex = 0; //Con_Printf ("---%i to %i ----\n", client->delta_sequence & UPDATE_MASK // , client->netchan.outgoing_sequence & UPDATE_MASK); while (newindex < to->numents || oldindex < oldmax) { newnum = newindex >= to->numents ? 9999 : to->entnum[newindex]; oldnum = oldindex >= oldmax ? 9999 : from->entnum[oldindex]; if (newnum == oldnum) { // delta update from old position //Con_Printf ("delta %i\n", newnum); SV_WriteDelta (newnum, &from->ents[oldindex], &to->ents[newindex], msg, false); oldindex++; newindex++; continue; } if (newnum < oldnum) { // this is a new entity, send it from the baseline baseline = &qtv->baseline[newnum]; //Con_Printf ("baseline %i\n", newnum); SV_WriteDelta (newnum, baseline, &to->ents[newindex], msg, true); newindex++; continue; } if (newnum > oldnum) { // the old entity isn't present in the new message //Con_Printf ("remove %i\n", oldnum); WriteShort (msg, oldnum | U_REMOVE); oldindex++; continue; } } WriteShort (msg, 0); // end of packetentities } void Prox_SendInitialEnts(sv_t *qtv, oproxy_t *prox, netmsg_t *msg) { int i; WriteByte(msg, svc_packetentities); for (i = 0; i < qtv->maxents; i++) SV_WriteDelta(i, &nullentstate, &qtv->curents[i], msg, true); WriteShort(msg, 0); } static float InterpolateAngle(float current, float ideal, float fraction) { float move; move = ideal - current; if (move >= 32767) move -= 65535; else if (move <= -32767) move += 65535; return current + fraction * move; } void SendPlayerStates(sv_t *tv, viewer_t *v, netmsg_t *msg) { packet_entities_t *e; int i; usercmd_t to; unsigned short flags; short interp; float lerp; memset(&to, 0, sizeof(to)); lerp = ((tv->curtime - tv->oldpackettime)/1000.0f) / ((tv->nextpackettime - tv->oldpackettime)/1000.0f); if (lerp < 0) lerp = 0; if (lerp > 1) lerp = 1; for (i = 0; i < MAX_CLIENTS-1; i++) { if (!tv->players[i].active) continue; flags = PF_COMMAND; if (v->trackplayer == i && tv->players[i].current.weaponframe) flags |= PF_WEAPONFRAME; WriteByte(msg, svc_playerinfo); WriteByte(msg, i); WriteShort(msg, flags); interp = (lerp)*tv->players[i].current.origin[0] + (1-lerp)*tv->players[i].old.origin[0]; WriteShort(msg, interp); interp = (lerp)*tv->players[i].current.origin[1] + (1-lerp)*tv->players[i].old.origin[1]; WriteShort(msg, interp); interp = (lerp)*tv->players[i].current.origin[2] + (1-lerp)*tv->players[i].old.origin[2]; WriteShort(msg, interp); WriteByte(msg, tv->players[i].current.frame); if (flags & PF_MSEC) { WriteByte(msg, 0); } if (flags & PF_COMMAND) { // to.angles[0] = tv->players[i].current.angles[0]; // to.angles[1] = tv->players[i].current.angles[1]; // to.angles[2] = tv->players[i].current.angles[2]; to.angles[0] = InterpolateAngle(tv->players[i].old.angles[0], tv->players[i].current.angles[0], lerp); to.angles[1] = InterpolateAngle(tv->players[i].old.angles[1], tv->players[i].current.angles[1], lerp); to.angles[2] = InterpolateAngle(tv->players[i].old.angles[2], tv->players[i].current.angles[2], lerp); WriteDeltaUsercmd(msg, &nullcmd, &to); } //vel //model //skin //effects //weaponframe if (flags & PF_WEAPONFRAME) WriteByte(msg, tv->players[i].current.weaponframe); } WriteByte(msg, svc_playerinfo); WriteByte(msg, MAX_CLIENTS-1); WriteShort(msg, 0); WriteShort(msg, v->origin[0]*8); WriteShort(msg, v->origin[1]*8); WriteShort(msg, v->origin[2]*8); WriteByte(msg, 0); e = &v->frame[v->netchan.outgoing_sequence&(ENTITY_FRAMES-1)]; e->numents = 0; for (i = 0; i < tv->maxents; i++) { if (!tv->curents[i].modelindex) continue; //FIXME: add interpolation. e->entnum[e->numents] = i; memcpy(&e->ents[e->numents], &tv->curents[i], sizeof(entity_state_t)); if (tv->entupdatetime[i] == tv->oldpackettime) { e->ents[e->numents].origin[0] = (lerp)*tv->curents[i].origin[0] + (1-lerp)*tv->oldents[i].origin[0]; e->ents[e->numents].origin[1] = (lerp)*tv->curents[i].origin[1] + (1-lerp)*tv->oldents[i].origin[1]; e->ents[e->numents].origin[2] = (lerp)*tv->curents[i].origin[2] + (1-lerp)*tv->oldents[i].origin[2]; } e->numents++; if (e->numents == ENTS_PER_FRAME) break; } SV_EmitPacketEntities(tv, v, e, msg); } void UpdateStats(sv_t *qtv, viewer_t *v) { netmsg_t msg; char buf[6]; int i; const static unsigned int nullstats[MAX_STATS]; const unsigned int *stats; InitNetMsg(&msg, buf, sizeof(buf)); if (v->trackplayer < 0) stats = nullstats; else stats = qtv->players[v->trackplayer].stats; for (i = 0; i < MAX_STATS; i++) { if (v->currentstats[i] != stats[i]) { if (stats[i] < 256) { WriteByte(&msg, svc_updatestat); WriteByte(&msg, i); WriteByte(&msg, stats[i]); } else { WriteByte(&msg, svc_updatestatlong); WriteByte(&msg, i); WriteLong(&msg, stats[i]); } SendBufferToViewer(v, msg.data, msg.cursize, true); msg.cursize = 0; v->currentstats[i] = stats[i]; } } } //returns the next prespawn 'buffer' number to use, or -1 if no more int Prespawn(sv_t *qtv, int curmsgsize, netmsg_t *msg, int bufnum) { int r, ni; r = bufnum; ni = SendCurrentUserinfos(qtv, curmsgsize, msg, bufnum); r += ni - bufnum; bufnum -= MAX_CLIENTS; ni = SendCurrentBaselines(qtv, curmsgsize, msg, bufnum); r += ni - bufnum; bufnum = ni; bufnum -= MAX_ENTITIES; ni = SendCurrentLightmaps(qtv, curmsgsize, msg, bufnum); r += ni - bufnum; bufnum = ni; bufnum -= MAX_LIGHTSTYLES; if (bufnum == 0) return -1; return r; } #define M_PI 3.1415926535897932384626433832795 #include void AngleVectors (short angles[3], float *forward, float *right, float *up) { float angle; float sr, sp, sy, cr, cp, cy; angle = angles[1] * (M_PI*2 / 65535); sy = sin(angle); cy = cos(angle); angle = angles[0] * (M_PI*2 / 65535); sp = sin(angle); cp = cos(angle); angle = angles[2] * (M_PI*2 / 65535); sr = sin(angle); cr = cos(angle); forward[0] = cp*cy; forward[1] = cp*sy; forward[2] = -sp; right[0] = (-1*sr*sp*cy+-1*cr*-sy); right[1] = (-1*sr*sp*sy+-1*cr*cy); right[2] = -1*sr*cp; up[0] = (cr*sp*cy+-sr*-sy); up[1] = (cr*sp*sy+-sr*cy); up[2] = cr*cp; } void PMove(viewer_t *v, usercmd_t *cmd) { int i; float fwd[3], rgt[3], up[3]; AngleVectors(cmd->angles, fwd, rgt, up); for (i = 0; i < 3; i++) v->origin[i] += (cmd->forwardmove*fwd[i] + cmd->sidemove*rgt[i] + cmd->upmove*up[i])*(cmd->msec/1000.0f); } void ParseQWC(sv_t *qtv, viewer_t *v, netmsg_t *m) { usercmd_t oldest, oldcmd, newcmd; char buf[1024]; netmsg_t msg; v->delta_frame = -1; while (m->readpos < m->cursize) { switch (ReadByte(m)) { case clc_nop: return; case clc_delta: v->delta_frame = ReadByte(m); break; case clc_stringcmd: ReadString (m, buf, sizeof(buf)); printf("stringcmd: %s\n", buf); if (!strcmp(buf, "new")) SendServerData(qtv, v); else if (!strncmp(buf, "modellist ", 10)) { char *cmd = buf+10; int svcount = atoi(cmd); int first; while((*cmd >= '0' && *cmd <= '9') || *cmd == '-') cmd++; first = atoi(cmd); InitNetMsg(&msg, buf, sizeof(buf)); if (svcount != qtv->servercount) { //looks like we changed map without them. SendServerData(qtv, v); return; } SendList(qtv, first, qtv->modellist, svc_modellist, &msg); SendBufferToViewer(v, msg.data, msg.cursize, true); } else if (!strncmp(buf, "soundlist ", 10)) { char *cmd = buf+10; int svcount = atoi(cmd); int first; while((*cmd >= '0' && *cmd <= '9') || *cmd == '-') cmd++; first = atoi(cmd); InitNetMsg(&msg, buf, sizeof(buf)); if (svcount != qtv->servercount) { //looks like we changed map without them. SendServerData(qtv, v); return; } SendList(qtv, first, qtv->soundlist, svc_soundlist, &msg); SendBufferToViewer(v, msg.data, msg.cursize, true); } else if (!strncmp(buf, "prespawn", 8)) { char skin[128]; if (atoi(buf + 9) != qtv->servercount) SendServerData(qtv, v); //we're old. else { int r; char *s; s = buf+9; while((*s >= '0' && *s <= '9') || *s == '-') s++; while(*s == ' ') s++; r = atoi(s); InitNetMsg(&msg, buf, sizeof(buf)); r = Prespawn(qtv, v->netchan.message.cursize, &msg, r); SendBufferToViewer(v, msg.data, msg.cursize, true); if (r < 0) sprintf(skin, "%ccmd spawn\n", svc_stufftext); else sprintf(skin, "%ccmd prespawn %i %i\n", svc_stufftext, qtv->servercount, r); SendBufferToViewer(v, skin, strlen(skin)+1, true); } } else if (!strncmp(buf, "spawn", 5)) { char skin[64]; sprintf(skin, "%cskins\n", svc_stufftext); SendBufferToViewer(v, skin, strlen(skin)+1, true); } else if (!strncmp(buf, "begin", 5)) { if (atoi(buf+6) != qtv->servercount) SendServerData(qtv, v); //this is unfortunate! else v->thinksitsconnected = true; } else if (!strncmp(buf, "download", 8)) { netmsg_t m; InitNetMsg(&m, buf, sizeof(buf)); WriteByte(&m, svc_download); WriteShort(&m, -1); WriteByte(&m, 0); SendBufferToViewer(v, m.data, m.cursize, true); } else if (!strncmp(buf, "drop", 4)) v->drop = true; else if (!strncmp(buf, "ptrack ", 7)) v->trackplayer = atoi(buf+7); else { printf("Client sent unknown string command: %s\n", buf); } break; case clc_move: ReadByte(m); ReadByte(m); ReadDeltaUsercmd(m, &nullcmd, &oldest); ReadDeltaUsercmd(m, &oldest, &oldcmd); ReadDeltaUsercmd(m, &oldcmd, &newcmd); PMove(v, &newcmd); break; case clc_tmove: v->origin[0] = ReadShort(m)/8.0f; v->origin[1] = ReadShort(m)/8.0f; v->origin[2] = ReadShort(m)/8.0f; break; case clc_upload: v->drop = true; return; default: v->drop = true; return; } } } const char dropcmd[] = {svc_stufftext, 'd', 'i', 's', 'c', 'o', 'n', 'n', 'e', 'c', 't', '\n', '\0'}; void QW_UpdateUDPStuff(sv_t *qtv) { char buffer[MAX_MSGLEN*2]; netadr_t from; int fromsize = sizeof(from); int read; int qport; netmsg_t m; viewer_t *v, *f; if (qtv->parsingconnectiondata) return; //don't accept new qw clients, no sending incompleate data, etc. m.data = buffer; m.cursize = 0; m.maxsize = MAX_MSGLEN; m.readpos = 0; for (;;) { read = recvfrom(qtv->qwdsocket, buffer, sizeof(buffer), 0, (struct sockaddr*)from, &fromsize); if (read <= 6) //otherwise it's a runt or bad. { if (read < 0) //it's bad. break; continue; } m.data = buffer; m.cursize = read; m.maxsize = MAX_MSGLEN; m.readpos = 0; if (*(int*)buffer == -1) { //connectionless message ConnectionlessPacket(qtv, &from, &m); continue; } //read the qport ReadLong(&m); ReadLong(&m); qport = ReadShort(&m); for (v = qtv->viewers; v; v = v->next) { if (Net_CompareAddress(&v->netchan.remote_address, &from, v->netchan.qport, qport)) { if (Netchan_Process(&v->netchan, &m)) { v->netchan.outgoing_sequence = v->netchan.incoming_sequence; //compensate for client->server packetloss. if (!v->chokeme) { v->maysend = true; if (qtv->chokeonnotupdated) v->chokeme = true; } ParseQWC(qtv, v, &m); } break; } } } if (qtv->viewers && qtv->viewers->drop) { printf("Dropping client\n"); f = qtv->viewers; qtv->viewers = f->next; Netchan_Transmit(&f->netchan, strlen(dropcmd)+1, dropcmd); Netchan_Transmit(&f->netchan, strlen(dropcmd)+1, dropcmd); Netchan_Transmit(&f->netchan, strlen(dropcmd)+1, dropcmd); free(f); } for (v = qtv->viewers; v; v = v->next) { if (v->next && v->next->drop) { //free the next/ printf("Dropping client\n"); f = v->next; v->next = f->next; Netchan_Transmit(&f->netchan, strlen(dropcmd)+1, dropcmd); Netchan_Transmit(&f->netchan, strlen(dropcmd)+1, dropcmd); Netchan_Transmit(&f->netchan, strlen(dropcmd)+1, dropcmd); free(f); } if (v->maysend) { v->maysend = false; m.cursize = 0; if (v->thinksitsconnected) { SendPlayerStates(qtv, v, &m); UpdateStats(qtv, v); } Netchan_Transmit(&v->netchan, m.cursize, m.data); if (!v->netchan.message.cursize && v->backbuffered) {//shift the backbuffers around memcpy(v->netchan.message.data, v->backbuf[0].data, v->backbuf[0].cursize); v->netchan.message.cursize = v->backbuf[0].cursize; for (read = 0; read < v->backbuffered; read++) { if (read == v->backbuffered-1) { v->backbuf[read].cursize = 0; } else { memcpy(v->backbuf[read].data, v->backbuf[read+1].data, v->backbuf[read+1].cursize); v->backbuf[read].cursize = v->backbuf[read+1].cursize; } } } } } }