mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-26 22:01:50 +00:00
cb3f383214
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@1976 fc73d0e0-1445-4013-8a0c-d673dee63da5
1976 lines
46 KiB
C
1976 lines
46 KiB
C
/*
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the included (GNU.txt) GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
|
|
#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, int servercount)
|
|
{
|
|
WriteByte(msg, svc_serverdata);
|
|
WriteLong(msg, PROTOCOL_VERSION);
|
|
WriteLong(msg, servercount);
|
|
|
|
if (!tv)
|
|
{
|
|
//dummy connection, for choosing a game to watch.
|
|
WriteString(msg, "qw");
|
|
|
|
if (mvd)
|
|
WriteFloat(msg, 0);
|
|
else
|
|
WriteByte(msg, MAX_CLIENTS-1);
|
|
WriteString(msg, "FTEQTV Proxy");
|
|
|
|
|
|
// get the movevars
|
|
WriteFloat(msg, 0);
|
|
WriteFloat(msg, 0);
|
|
WriteFloat(msg, 0);
|
|
WriteFloat(msg, 0);
|
|
WriteFloat(msg, 0);
|
|
WriteFloat(msg, 0);
|
|
WriteFloat(msg, 0);
|
|
WriteFloat(msg, 0);
|
|
WriteFloat(msg, 0);
|
|
WriteFloat(msg, 0);
|
|
|
|
|
|
|
|
WriteByte(msg, svc_stufftext);
|
|
WriteString2(msg, "fullserverinfo \"");
|
|
WriteString2(msg, "\\*QTV\\"VERSION);
|
|
WriteString(msg, "\"\n");
|
|
|
|
}
|
|
else
|
|
{
|
|
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, 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, viewer->servercount);
|
|
|
|
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;
|
|
|
|
WriteByte(msg, svc_updateuserinfo);
|
|
WriteByte(msg, MAX_CLIENTS-1);
|
|
WriteLong(msg, MAX_CLIENTS-1);
|
|
WriteString(msg, "\\*spectator\\1\\name\\YOU!");
|
|
|
|
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_updatefrags);
|
|
WriteByte(msg, i);
|
|
WriteShort(msg, tv->players[i].frags);
|
|
|
|
WriteByte(msg, svc_updateping);
|
|
WriteByte(msg, i);
|
|
WriteShort(msg, tv->players[i].ping);
|
|
|
|
WriteByte(msg, svc_updatepl);
|
|
WriteByte(msg, i);
|
|
WriteByte(msg, tv->players[i].packetloss);
|
|
}
|
|
|
|
i++;
|
|
|
|
return i;
|
|
}
|
|
void WriteEntityState(netmsg_t *msg, entity_state_t *es)
|
|
{
|
|
int i;
|
|
WriteByte(msg, es->modelindex);
|
|
WriteByte(msg, es->frame);
|
|
WriteByte(msg, es->colormap);
|
|
WriteByte(msg, es->skinnum);
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
WriteShort(msg, es->origin[i]);
|
|
WriteByte(msg, es->angles[i]);
|
|
}
|
|
}
|
|
int SendCurrentBaselines(sv_t *tv, int cursize, netmsg_t *msg, int i)
|
|
{
|
|
|
|
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);
|
|
WriteEntityState(msg, &tv->entity[i].baseline);
|
|
}
|
|
|
|
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 SendStaticSounds(sv_t *tv, int cursize, netmsg_t *msg, int i)
|
|
{
|
|
if (i < 0 || i >= MAX_STATICSOUNDS)
|
|
return i;
|
|
|
|
for (; i < MAX_STATICSOUNDS; i++)
|
|
{
|
|
if (msg->cursize+cursize+16 > 768)
|
|
{
|
|
return i;
|
|
}
|
|
if (!tv->staticsound[i].soundindex)
|
|
continue;
|
|
|
|
WriteByte(msg, svc_spawnstaticsound);
|
|
WriteShort(msg, tv->staticsound[i].origin[0]);
|
|
WriteShort(msg, tv->staticsound[i].origin[1]);
|
|
WriteShort(msg, tv->staticsound[i].origin[2]);
|
|
WriteByte(msg, tv->staticsound[i].soundindex);
|
|
WriteByte(msg, tv->staticsound[i].volume);
|
|
WriteByte(msg, tv->staticsound[i].attenuation);
|
|
}
|
|
|
|
return i;
|
|
}
|
|
int SendStaticEntities(sv_t *tv, int cursize, netmsg_t *msg, int i)
|
|
{
|
|
if (i < 0 || i >= MAX_STATICENTITIES)
|
|
return i;
|
|
|
|
for (; i < MAX_STATICENTITIES; i++)
|
|
{
|
|
if (msg->cursize+cursize+16 > 768)
|
|
{
|
|
return i;
|
|
}
|
|
if (!tv->spawnstatic[i].modelindex)
|
|
continue;
|
|
|
|
WriteByte(msg, svc_spawnstatic);
|
|
WriteEntityState(msg, &tv->spawnstatic[i]);
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
int SendList(sv_t *qtv, int first, const 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;
|
|
}
|
|
|
|
//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 NewQWClient(cluster_t *cluster, netadr_t *addr, char *connectmessage)
|
|
{
|
|
viewer_t *viewer;
|
|
|
|
char qport[32];
|
|
char challenge[32];
|
|
char infostring[256];
|
|
|
|
connectmessage+=11;
|
|
|
|
connectmessage = COM_ParseToken(connectmessage, qport, sizeof(qport), "");
|
|
connectmessage = COM_ParseToken(connectmessage, challenge, sizeof(challenge), "");
|
|
connectmessage = COM_ParseToken(connectmessage, infostring, sizeof(infostring), "");
|
|
|
|
if (!ChallengePasses(addr, atoi(challenge)))
|
|
{
|
|
Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *addr, "n" "Bad challenge");
|
|
return;
|
|
}
|
|
|
|
|
|
viewer = malloc(sizeof(viewer_t));
|
|
memset(viewer, 0, sizeof(viewer_t));
|
|
|
|
viewer->trackplayer = -1;
|
|
Netchan_Setup (cluster->qwdsocket, &viewer->netchan, *addr, atoi(qport));
|
|
|
|
viewer->next = cluster->viewers;
|
|
cluster->viewers = viewer;
|
|
viewer->delta_frame = -1;
|
|
|
|
if (cluster->numservers == 1)
|
|
{
|
|
viewer->server = cluster->servers;
|
|
if (!viewer->server->modellist[1].name[0])
|
|
viewer->server = NULL; //damn, that server isn't ready
|
|
}
|
|
|
|
if (!viewer->server)
|
|
viewer->menunum = 1;
|
|
|
|
cluster->numviewers++;
|
|
|
|
Info_ValueForKey(infostring, "name", viewer->name, sizeof(viewer->name));
|
|
|
|
Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *addr, "j");
|
|
|
|
QW_PrintfToViewer(viewer, "Welcome to FTEQTV\n");
|
|
QW_StuffcmdToViewer(viewer, "alias admin \"cmd admin\"\n");
|
|
QW_PrintfToViewer(viewer, "Type admin for the admin menu\n");
|
|
}
|
|
|
|
void QTV_Rcon(cluster_t *cluster, char *message, netadr_t *from)
|
|
{
|
|
char buffer[8192];
|
|
|
|
char *command;
|
|
int passlen;
|
|
|
|
if (!*cluster->password)
|
|
{
|
|
Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n" "Bad rcon_password.\n");
|
|
return;
|
|
}
|
|
|
|
while(*message > '\0' && *message <= ' ')
|
|
message++;
|
|
|
|
command = strchr(message, ' ');
|
|
passlen = command-message;
|
|
if (passlen != strlen(cluster->password) || strncmp(message, cluster->password, passlen))
|
|
{
|
|
Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n" "Bad rcon_password.\n");
|
|
return;
|
|
}
|
|
|
|
Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n%s", Rcon_Command(cluster, NULL, command, buffer, sizeof(buffer), false));
|
|
}
|
|
|
|
void QTV_Status(cluster_t *cluster, netadr_t *from)
|
|
{
|
|
int i;
|
|
char buffer[8192];
|
|
sv_t *sv;
|
|
|
|
netmsg_t msg;
|
|
char elem[256];
|
|
InitNetMsg(&msg, buffer, sizeof(buffer));
|
|
WriteLong(&msg, -1);
|
|
WriteByte(&msg, 'n');
|
|
|
|
if (cluster->numservers==1)
|
|
{ //show this server's info
|
|
sv = cluster->servers;
|
|
|
|
WriteString2(&msg, sv->serverinfo);
|
|
WriteString2(&msg, "\n");
|
|
|
|
for (i = 0;i < MAX_CLIENTS-1; i++)
|
|
{
|
|
if (!sv->players[i].active)
|
|
continue;
|
|
//userid
|
|
sprintf(elem, "%i", i);
|
|
WriteString2(&msg, elem);
|
|
WriteString2(&msg, " ");
|
|
|
|
//frags
|
|
sprintf(elem, "%i", sv->players[i].frags);
|
|
WriteString2(&msg, elem);
|
|
WriteString2(&msg, " ");
|
|
|
|
//time (minuites)
|
|
sprintf(elem, "%i", 0);
|
|
WriteString2(&msg, elem);
|
|
WriteString2(&msg, " ");
|
|
|
|
//ping
|
|
sprintf(elem, "%i", sv->players[i].ping);
|
|
WriteString2(&msg, elem);
|
|
WriteString2(&msg, " ");
|
|
|
|
//name
|
|
Info_ValueForKey(sv->players[i].userinfo, "name", elem, sizeof(elem));
|
|
WriteString2(&msg, "\"");
|
|
WriteString2(&msg, elem);
|
|
WriteString2(&msg, "\" ");
|
|
|
|
//skin
|
|
Info_ValueForKey(sv->players[i].userinfo, "skin", elem, sizeof(elem));
|
|
WriteString2(&msg, "\"");
|
|
WriteString2(&msg, elem);
|
|
WriteString2(&msg, "\" ");
|
|
WriteString2(&msg, " ");
|
|
|
|
//tc
|
|
Info_ValueForKey(sv->players[i].userinfo, "topcolor", elem, sizeof(elem));
|
|
WriteString2(&msg, elem);
|
|
WriteString2(&msg, " ");
|
|
|
|
//bc
|
|
Info_ValueForKey(sv->players[i].userinfo, "bottomcolor", elem, sizeof(elem));
|
|
WriteString2(&msg, elem);
|
|
WriteString2(&msg, " ");
|
|
|
|
WriteString2(&msg, "\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WriteString2(&msg, "\\hostname\\");
|
|
WriteString2(&msg, cluster->hostname);
|
|
|
|
for (sv = cluster->servers, i = 0; sv; sv = sv->next, i++)
|
|
{
|
|
sprintf(elem, "\\%i\\", i);
|
|
WriteString2(&msg, elem);
|
|
WriteString2(&msg, sv->serveraddress);
|
|
sprintf(elem, " (%s)", sv->serveraddress);
|
|
WriteString2(&msg, sv->serveraddress);
|
|
}
|
|
}
|
|
|
|
WriteByte(&msg, 0);
|
|
NET_SendPacket(cluster, cluster->qwdsocket, msg.cursize, msg.data, *from);
|
|
}
|
|
|
|
|
|
void ConnectionlessPacket(cluster_t *cluster, netadr_t *from, netmsg_t *m)
|
|
{
|
|
char buffer[MAX_MSGLEN];
|
|
int i;
|
|
|
|
ReadLong(m);
|
|
ReadString(m, buffer, sizeof(buffer));
|
|
|
|
if (!strncmp(buffer, "rcon ", 5))
|
|
{
|
|
QTV_Rcon(cluster, buffer+5, from);
|
|
return;
|
|
}
|
|
if (!strncmp(buffer, "ping", 4))
|
|
{ //ack
|
|
NET_SendPacket (cluster, cluster->qwdsocket, 1, "l", *from);
|
|
return;
|
|
}
|
|
if (!strncmp(buffer, "status", 6))
|
|
{
|
|
QTV_Status(cluster, from);
|
|
return;
|
|
}
|
|
if (!strncmp(buffer, "getchallenge", 12))
|
|
{
|
|
i = NewChallenge(from);
|
|
Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "c%i", i);
|
|
return;
|
|
}
|
|
if (!strncmp(buffer, "connect 28 ", 11))
|
|
{
|
|
if (cluster->numviewers >= cluster->maxviewers && cluster->maxviewers)
|
|
Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n" "Sorry, proxy is full.\n");
|
|
else
|
|
NewQWClient(cluster, from, buffer);
|
|
return;
|
|
}
|
|
if (!strncmp(buffer, "l\n", 2))
|
|
{
|
|
Sys_Printf(cluster, "Ack\n");
|
|
}
|
|
}
|
|
|
|
|
|
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->entity[newnum].baseline;
|
|
//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++)
|
|
{
|
|
if (qtv->entity[i].current.modelindex)
|
|
SV_WriteDelta(i, &nullentstate, &qtv->entity[i].current, 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));
|
|
|
|
if (v->server)
|
|
{
|
|
if (tv->physicstime != v->settime && tv->cluster->chokeonnotupdated)
|
|
{
|
|
WriteByte(msg, svc_updatestatlong);
|
|
WriteByte(msg, STAT_TIME);
|
|
WriteLong(msg, v->settime);
|
|
|
|
v->settime = tv->physicstime;
|
|
}
|
|
|
|
BSP_SetupForPosition(tv->bsp, v->origin[0], v->origin[1], v->origin[2]);
|
|
|
|
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;
|
|
|
|
if (v->trackplayer != i && !BSP_Visible(tv->bsp, tv->players[i].leafcount, tv->players[i].leafs))
|
|
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);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lerp = 1;
|
|
}
|
|
|
|
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;
|
|
if (v->server)
|
|
for (i = 0; i < tv->maxents; i++)
|
|
{
|
|
if (!tv->entity[i].current.modelindex)
|
|
continue;
|
|
|
|
if (tv->entity[i].current.modelindex >= tv->numinlines && !BSP_Visible(tv->bsp, tv->entity[i].leafcount, tv->entity[i].leafs))
|
|
continue;
|
|
|
|
e->entnum[e->numents] = i;
|
|
memcpy(&e->ents[e->numents], &tv->entity[i].current, sizeof(entity_state_t));
|
|
|
|
if (tv->entity[i].updatetime == tv->oldpackettime)
|
|
{
|
|
e->ents[e->numents].origin[0] = (lerp)*tv->entity[i].current.origin[0] + (1-lerp)*tv->entity[i].old.origin[0];
|
|
e->ents[e->numents].origin[1] = (lerp)*tv->entity[i].current.origin[1] + (1-lerp)*tv->entity[i].old.origin[1];
|
|
e->ents[e->numents].origin[2] = (lerp)*tv->entity[i].current.origin[2] + (1-lerp)*tv->entity[i].old.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] = {1000};
|
|
|
|
const unsigned int *stats;
|
|
|
|
InitNetMsg(&msg, buf, sizeof(buf));
|
|
|
|
if (v->trackplayer < 0 || !qtv)
|
|
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 = ni;
|
|
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;
|
|
|
|
ni = SendStaticSounds(qtv, curmsgsize, msg, bufnum);
|
|
r += ni - bufnum;
|
|
bufnum = ni;
|
|
bufnum -= MAX_STATICSOUNDS;
|
|
|
|
ni = SendStaticEntities(qtv, curmsgsize, msg, bufnum);
|
|
r += ni - bufnum;
|
|
bufnum = ni;
|
|
bufnum -= MAX_STATICENTITIES;
|
|
|
|
if (bufnum == 0)
|
|
return -1;
|
|
|
|
return r;
|
|
}
|
|
|
|
#define M_PI 3.1415926535897932384626433832795
|
|
#include <math.h>
|
|
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 QTV_Say(cluster_t *cluster, sv_t *qtv, viewer_t *v, char *message)
|
|
{
|
|
char buf[1024];
|
|
netmsg_t msg;
|
|
|
|
if (message[strlen(message)-1] == '\"')
|
|
message[strlen(message)-1] = '\0';
|
|
|
|
if (*v->expectcommand && v->menunum)
|
|
{
|
|
buf[sizeof(buf)-1] = '\0';
|
|
if (!strcmp(v->expectcommand, "hostname"))
|
|
{
|
|
strncpy(cluster->hostname, message, sizeof(cluster->hostname));
|
|
cluster->hostname[sizeof(cluster->hostname)-1] = '\0';
|
|
}
|
|
else if (!strcmp(v->expectcommand, "master"))
|
|
{
|
|
strncpy(cluster->master, message, sizeof(cluster->master));
|
|
cluster->master[sizeof(cluster->master)-1] = '\0';
|
|
if (!strcmp(cluster->master, "."))
|
|
*cluster->master = '\0';
|
|
cluster->mastersendtime = cluster->curtime;
|
|
}
|
|
else if (!strcmp(v->expectcommand, "addserver"))
|
|
{
|
|
_snprintf(buf, sizeof(buf), "tcp:%s", message);
|
|
qtv = QTV_NewServerConnection(cluster, buf, false);
|
|
if (qtv)
|
|
{
|
|
v->server = qtv;
|
|
QW_StuffcmdToViewer(v, "cmd new\n");
|
|
QW_PrintfToViewer(v, "Connected\n", message);
|
|
}
|
|
else
|
|
QW_PrintfToViewer(v, "Failed to connect to server \"%s\", connection aborted\n", message);
|
|
}
|
|
else if (!strcmp(v->expectcommand, "admin"))
|
|
{
|
|
if (!strcmp(message, cluster->password))
|
|
{
|
|
v->menunum = 2;
|
|
v->isadmin = true;
|
|
Sys_Printf(cluster, "Player %s logs in as admin\n", v->name);
|
|
}
|
|
else
|
|
{
|
|
QW_PrintfToViewer(v, "Admin password incorrect\n");
|
|
Sys_Printf(cluster, "Player %s gets incorrect admin password\n", v->name);
|
|
}
|
|
}
|
|
else if (!strcmp(v->expectcommand, "adddemo"))
|
|
{
|
|
_snprintf(buf, sizeof(buf), "file:%s", message);
|
|
qtv = QTV_NewServerConnection(cluster, buf, false);
|
|
if (!qtv)
|
|
QW_PrintfToViewer(v, "Failed to play demo \"%s\"\n", message);
|
|
else
|
|
{
|
|
v->server = qtv;
|
|
QW_StuffcmdToViewer(v, "cmd new\n");
|
|
QW_PrintfToViewer(v, "Opened demo file.\n", message);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QW_PrintfToViewer(v, "Command %s was not recognised\n", v->expectcommand);
|
|
}
|
|
|
|
*v->expectcommand = '\0';
|
|
return;
|
|
}
|
|
*v->expectcommand = '\0';
|
|
|
|
InitNetMsg(&msg, buf, sizeof(buf));
|
|
|
|
WriteByte(&msg, svc_print);
|
|
WriteByte(&msg, 3); //PRINT_CHAT
|
|
WriteString2(&msg, v->name);
|
|
WriteString2(&msg, "\x8d ");
|
|
WriteString2(&msg, message);
|
|
WriteString(&msg, "\n");
|
|
|
|
Broadcast(cluster, msg.data, msg.cursize);
|
|
}
|
|
|
|
viewer_t *QW_IsOn(cluster_t *cluster, char *name)
|
|
{
|
|
viewer_t *v;
|
|
for (v = cluster->viewers; v; v = v->next)
|
|
if (!strcmp(v->name, name)) //this needs to allow dequakified names.
|
|
return v;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void QW_PrintfToViewer(viewer_t *v, char *format, ...)
|
|
{
|
|
va_list argptr;
|
|
char buf[1024];
|
|
netmsg_t msg;
|
|
InitNetMsg(&msg, buf, sizeof(buf));
|
|
|
|
va_start (argptr, format);
|
|
#ifdef _WIN32
|
|
_vsnprintf (buf+2, sizeof(buf) - 3, format, argptr);
|
|
buf[sizeof(buf) - 3] = '\0';
|
|
#else
|
|
vsnprintf (buf+2, sizeof(buf)-2, format, argptr);
|
|
#endif // _WIN32
|
|
va_end (argptr);
|
|
|
|
buf[0] = svc_print;
|
|
buf[1] = 2; //PRINT_HIGH
|
|
|
|
SendBufferToViewer(v, buf, strlen(buf)+1, true);
|
|
}
|
|
|
|
|
|
void QW_StuffcmdToViewer(viewer_t *v, char *format, ...)
|
|
{
|
|
va_list argptr;
|
|
char buf[1024];
|
|
netmsg_t msg;
|
|
InitNetMsg(&msg, buf, sizeof(buf));
|
|
|
|
va_start (argptr, format);
|
|
#ifdef _WIN32
|
|
_vsnprintf (buf+1, sizeof(buf) - 2, format, argptr);
|
|
buf[sizeof(buf) - 2] = '\0';
|
|
#else
|
|
vsnprintf (buf+1, sizeof(buf)-1, format, argptr);
|
|
#endif // _WIN32
|
|
va_end (argptr);
|
|
|
|
buf[0] = svc_stufftext;
|
|
|
|
SendBufferToViewer(v, buf, strlen(buf)+1, true);
|
|
}
|
|
|
|
static const filename_t ConnectionlessModelList[] = {{""}, {"maps/start.bsp"}, {"progs/player.mdl"}, {""}};
|
|
static const filename_t ConnectionlessSoundList[] = {{""}, {""}};
|
|
|
|
void Menu_Enter(cluster_t *cluster, viewer_t *viewer, int buttonnum);
|
|
|
|
|
|
void ParseQWC(cluster_t *cluster, 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 != v->servercount)
|
|
{ //looks like we changed map without them.
|
|
SendServerData(qtv, v);
|
|
return;
|
|
}
|
|
|
|
if (!qtv)
|
|
SendList(qtv, first, ConnectionlessModelList, svc_modellist, &msg);
|
|
else
|
|
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 != v->servercount)
|
|
{ //looks like we changed map without them.
|
|
SendServerData(qtv, v);
|
|
return;
|
|
}
|
|
|
|
if (!qtv)
|
|
SendList(qtv, first, ConnectionlessSoundList, svc_soundlist, &msg);
|
|
else
|
|
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) != v->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));
|
|
|
|
if (v->server)
|
|
{
|
|
r = Prespawn(qtv, v->netchan.message.cursize, &msg, r);
|
|
SendBufferToViewer(v, msg.data, msg.cursize, true);
|
|
}
|
|
else r = -1;
|
|
|
|
if (r < 0)
|
|
sprintf(skin, "%ccmd spawn\n", svc_stufftext);
|
|
else
|
|
sprintf(skin, "%ccmd prespawn %i %i\n", svc_stufftext, v->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) != v->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, "ison", 4))
|
|
{
|
|
viewer_t *other;
|
|
if ((other = QW_IsOn(qtv->cluster, buf+5)))
|
|
{
|
|
if (!other->server)
|
|
QW_PrintfToViewer(v, "%s is on the proxy, but not yet watching a game\n");
|
|
else
|
|
QW_PrintfToViewer(v, "%s is watching %s\n", buf+5, other->server->server);
|
|
}
|
|
else
|
|
QW_PrintfToViewer(v, "%s is not on the proxy, sorry\n", buf+5); //the apology is to make the alternatives distinct.
|
|
}
|
|
else if (!strncmp(buf, "ptrack ", 7))
|
|
v->trackplayer = atoi(buf+7);
|
|
else if (!strncmp(buf, "ptrack", 6))
|
|
v->trackplayer = -1;
|
|
else if (!strncmp(buf, "pings", 5))
|
|
{
|
|
}
|
|
else if (!strncmp(buf, "say \"", 5) && !cluster->notalking)
|
|
QTV_Say(cluster, qtv, v, buf+5);
|
|
else if (!strncmp(buf, "say ", 4) && !cluster->notalking)
|
|
QTV_Say(cluster, qtv, v, buf+4);
|
|
|
|
else if (!strncmp(buf, "servers", 7))
|
|
{
|
|
v->menunum = 1;
|
|
}
|
|
else if (!strncmp(buf, "reset", 5))
|
|
{
|
|
QW_StuffcmdToViewer(v, "cmd new\n");
|
|
v->server = NULL;
|
|
v->menunum = 1;
|
|
}
|
|
else if (!strncmp(buf, "admin", 5))
|
|
{
|
|
if (!*cluster->password)
|
|
{
|
|
if (Netchan_IsLocal(v->netchan.remote_address))
|
|
{
|
|
Sys_Printf(cluster, "Local player %s logs in as admin\n", v->name);
|
|
v->menunum = 2;
|
|
v->isadmin = true;
|
|
}
|
|
else
|
|
QW_PrintfToViewer(v, "There is no admin password set\nYou may not log in.\n");
|
|
}
|
|
else if (v->isadmin)
|
|
v->menunum = 2;
|
|
else
|
|
{
|
|
strcpy(v->expectcommand, "admin");
|
|
QW_StuffcmdToViewer(v, "echo Please enter the rcon password\nmessagemode\n");
|
|
}
|
|
}
|
|
|
|
else if (!qtv)
|
|
{
|
|
//all the other things need an active server.
|
|
QW_PrintfToViewer(v, "Choose a server first\n");
|
|
}
|
|
else if (!strncmp(buf, "serverinfo", 5))
|
|
{
|
|
char *key, *value, *end;
|
|
int len;
|
|
netmsg_t m;
|
|
InitNetMsg(&m, buf, sizeof(buf));
|
|
WriteByte(&m, svc_print);
|
|
WriteByte(&m, 2);
|
|
end = qtv->serverinfo;
|
|
for(;;)
|
|
{
|
|
if (!*end)
|
|
break;
|
|
key = end;
|
|
value = strchr(key+1, '\\');
|
|
if (!value)
|
|
break;
|
|
end = strchr(value+1, '\\');
|
|
if (!end)
|
|
end = value+strlen(value);
|
|
|
|
len = value-key;
|
|
|
|
key++;
|
|
while(*key != '\\' && *key)
|
|
WriteByte(&m, *key++);
|
|
|
|
for (; len < 20; len++)
|
|
WriteByte(&m, ' ');
|
|
|
|
value++;
|
|
while(*value != '\\' && *value)
|
|
WriteByte(&m, *value++);
|
|
WriteByte(&m, '\n');
|
|
}
|
|
WriteByte(&m, 0);
|
|
|
|
// WriteString(&m, qtv->serverinfo);
|
|
SendBufferToViewer(v, m.data, m.cursize, true);
|
|
}
|
|
else
|
|
{
|
|
Sys_Printf(cluster, "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);
|
|
|
|
if (v->menunum)
|
|
{
|
|
if (newcmd.buttons&1 && !(oldcmd.buttons&1))
|
|
Menu_Enter(cluster, v, 0);
|
|
if (newcmd.buttons&2 && !(oldcmd.buttons&2))
|
|
Menu_Enter(cluster, v, 1);
|
|
if (newcmd.sidemove && !oldcmd.sidemove)
|
|
Menu_Enter(cluster, v, newcmd.sidemove<0);
|
|
|
|
if (newcmd.forwardmove && !oldcmd.forwardmove)
|
|
{ //they pressed the button...
|
|
if (newcmd.forwardmove < 0)
|
|
{
|
|
v->menuop+=1;
|
|
}
|
|
else
|
|
{
|
|
v->menuop-=1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
PMove(v, &newcmd);
|
|
break;
|
|
case clc_tmove:
|
|
v->origin[0] = ((signed short)ReadShort(m))/8.0f;
|
|
v->origin[1] = ((signed short)ReadShort(m))/8.0f;
|
|
v->origin[2] = ((signed short)ReadShort(m))/8.0f;
|
|
break;
|
|
|
|
case clc_upload:
|
|
v->drop = true;
|
|
return;
|
|
|
|
default:
|
|
v->drop = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Menu_Enter(cluster_t *cluster, viewer_t *viewer, int buttonnum)
|
|
{
|
|
//build a possible message, even though it'll probably not be sent
|
|
|
|
sv_t *sv;
|
|
int i, min;
|
|
char buffer[2048];
|
|
netmsg_t m;
|
|
InitNetMsg(&m, buffer, sizeof(buffer));
|
|
WriteByte(&m, svc_stufftext);
|
|
|
|
switch(viewer->menunum)
|
|
{
|
|
default:
|
|
break;
|
|
|
|
case 3:
|
|
if (viewer->server)
|
|
{
|
|
i = 0;
|
|
if (i++ == viewer->menuop)
|
|
{ //mvd port
|
|
QW_StuffcmdToViewer(viewer, "echo You will need to reconnect\n");
|
|
cluster->qwlistenportnum += buttonnum?-1:1;
|
|
}
|
|
if (i++ == viewer->menuop)
|
|
{ //disconnect
|
|
QTV_Shutdown(viewer->server);
|
|
}
|
|
if (i++ == viewer->menuop)
|
|
{ //back
|
|
viewer->menunum = 2;
|
|
viewer->menuop = 0;
|
|
}
|
|
break;
|
|
}
|
|
//fallthrough
|
|
case 1:
|
|
if (!cluster->servers)
|
|
{
|
|
WriteString(&m, "messagemode\n");
|
|
SendBufferToViewer(viewer, m.data, m.cursize, true);
|
|
}
|
|
else
|
|
{
|
|
if (viewer->menuop < 0)
|
|
viewer->menuop = 0;
|
|
i = 0;
|
|
min = viewer->menuop - 10;
|
|
if (min < 0)
|
|
min = 0;
|
|
for (sv = cluster->servers; sv && i<min; sv = sv->next, i++)
|
|
{//skip over the early connections.
|
|
}
|
|
min+=20;
|
|
for (; sv && i < min; sv = sv->next, i++)
|
|
{
|
|
if (i == viewer->menuop)
|
|
{
|
|
if (sv->parsingconnectiondata || !sv->modellist[1].name[0])
|
|
{
|
|
WriteString(&m, "echo But that stream isn't connected\n");
|
|
SendBufferToViewer(viewer, m.data, m.cursize, true);
|
|
}
|
|
else
|
|
{
|
|
WriteString(&m, "cmd new\n");
|
|
viewer->servercount++;
|
|
viewer->server = sv;
|
|
SendBufferToViewer(viewer, m.data, m.cursize, true);
|
|
viewer->menunum = 0;
|
|
viewer->thinksitsconnected = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 2:
|
|
i = 0;
|
|
if (i++ == viewer->menuop)
|
|
{ //connection stuff
|
|
viewer->menunum = 3;
|
|
viewer->menuop = 0;
|
|
}
|
|
if (i++ == viewer->menuop)
|
|
{ //qw port
|
|
QW_StuffcmdToViewer(viewer, "echo You will need to reconnect\n");
|
|
cluster->qwlistenportnum += buttonnum?-1:1;
|
|
}
|
|
if (i++ == viewer->menuop)
|
|
{ //hostname
|
|
strcpy(viewer->expectcommand, "hostname");
|
|
QW_StuffcmdToViewer(viewer, "echo Please enter the new hostname\nmessagemode\n");
|
|
}
|
|
if (i++ == viewer->menuop)
|
|
{ //master
|
|
strcpy(viewer->expectcommand, "master");
|
|
QW_StuffcmdToViewer(viewer, "echo Please enter the master dns or ip\necho Enter '.' for masterless mode\nmessagemode\n");
|
|
}
|
|
if (i++ == viewer->menuop)
|
|
{ //password
|
|
strcpy(viewer->expectcommand, "password");
|
|
QW_StuffcmdToViewer(viewer, "echo Please enter the new rcon password\nmessagemode\n");
|
|
}
|
|
if (i++ == viewer->menuop)
|
|
{ //add server
|
|
strcpy(viewer->expectcommand, "messagemode");
|
|
QW_StuffcmdToViewer(viewer, "echo Please enter the new qtv server dns or ip\naddserver\n");
|
|
}
|
|
if (i++ == viewer->menuop)
|
|
{ //add demo
|
|
strcpy(viewer->expectcommand, "adddemo");
|
|
QW_StuffcmdToViewer(viewer, "echo Please enter the name of the demo to play\nmessagemode\n");
|
|
}
|
|
if (i++ == viewer->menuop)
|
|
{ //choke
|
|
cluster->chokeonnotupdated ^= 1;
|
|
}
|
|
if (i++ == viewer->menuop)
|
|
{ //late forwarding
|
|
cluster->lateforward ^= 1;
|
|
}
|
|
if (i++ == viewer->menuop)
|
|
{ //no talking
|
|
cluster->notalking ^= 1;
|
|
}
|
|
if (i++ == viewer->menuop)
|
|
{ //nobsp
|
|
cluster->nobsp ^= 1;
|
|
}
|
|
if (i++ == viewer->menuop)
|
|
{ //back
|
|
viewer->menunum = 0;
|
|
viewer->menuop = 0;
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Menu_Draw(cluster_t *cluster, viewer_t *viewer)
|
|
{
|
|
char buffer[2048];
|
|
char str[64];
|
|
sv_t *sv;
|
|
int i, min;
|
|
unsigned char *s;
|
|
|
|
netmsg_t m;
|
|
InitNetMsg(&m, buffer, sizeof(buffer));
|
|
|
|
WriteByte(&m, svc_centerprint);
|
|
|
|
WriteString2(&m, "FTEQTV\n");
|
|
if (strcmp(cluster->hostname, DEFAULT_HOSTNAME))
|
|
WriteString2(&m, cluster->hostname);
|
|
|
|
switch(viewer->menunum)
|
|
{
|
|
default:
|
|
WriteString2(&m, "bad menu");
|
|
break;
|
|
|
|
case 3: //per-connection options
|
|
if (viewer->server)
|
|
{
|
|
sv = viewer->server;
|
|
WriteString2(&m, "\n\nConnection Admin\n");
|
|
WriteString2(&m, sv->hostname);
|
|
if (sv->file)
|
|
WriteString2(&m, " (demo)");
|
|
WriteString2(&m, "\n\n");
|
|
|
|
if (viewer->menuop < 0)
|
|
viewer->menuop = 0;
|
|
|
|
i = 0;
|
|
WriteString2(&m, " port");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20i", sv->tcplistenportnum);
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
WriteString2(&m, " disconnect");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20s", "...");
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
WriteString2(&m, " back");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20s", "...");
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
if (viewer->menuop >= i)
|
|
viewer->menuop = i - 1;
|
|
|
|
break;
|
|
}
|
|
//fallthrough
|
|
case 1: //connections list
|
|
|
|
WriteString2(&m, "\n\nServers\n\n");
|
|
|
|
if (!cluster->servers)
|
|
{
|
|
WriteString2(&m, "No active connections");
|
|
}
|
|
else
|
|
{
|
|
if (viewer->menuop < 0)
|
|
viewer->menuop = 0;
|
|
i = 0;
|
|
min = viewer->menuop - 10;
|
|
if (min < 0)
|
|
min = 0;
|
|
for (sv = cluster->servers; sv && i<min; sv = sv->next, i++)
|
|
{//skip over the early connections.
|
|
}
|
|
min+=20;
|
|
for (; sv && i < min; sv = sv->next, i++)
|
|
{
|
|
Info_ValueForKey(sv->serverinfo, "hostname", str, sizeof(str));
|
|
if (sv->parsingconnectiondata || !sv->modellist[1].name[0])
|
|
snprintf(str, sizeof(str), "%s", sv->server);
|
|
|
|
if (i == viewer->menuop)
|
|
for (s = (unsigned char *)str; *s; s++)
|
|
{
|
|
if ((unsigned)*s >= ' ')
|
|
*s = 128 | (*s&~128);
|
|
}
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 2: //admin menu
|
|
|
|
WriteString2(&m, "\n\nCluster Admin\n\n");
|
|
|
|
if (viewer->menuop < 0)
|
|
viewer->menuop = 0;
|
|
i = 0;
|
|
|
|
WriteString2(&m, " this connection");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20s", "...");
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
WriteString2(&m, " port");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20i", cluster->qwlistenportnum);
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
WriteString2(&m, " hostname");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20s", cluster->hostname);
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
WriteString2(&m, " master");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20s", cluster->master);
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
WriteString2(&m, " password");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20s", "...");
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
WriteString2(&m, " add server");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20s", "...");
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
WriteString2(&m, " add demo");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20s", "...");
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
WriteString2(&m, " choke");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20s", cluster->chokeonnotupdated?"yes":"no");
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
WriteString2(&m, "delay forwarding");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20s", cluster->lateforward?"yes":"no");
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
WriteString2(&m, " talking");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20s", cluster->notalking?"no":"yes");
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
WriteString2(&m, " nobsp");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20s", cluster->nobsp?"yes":"no");
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
WriteString2(&m, " back");
|
|
WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
|
|
sprintf(str, "%-20s", "...");
|
|
WriteString2(&m, str);
|
|
WriteString2(&m, "\n");
|
|
|
|
if (viewer->menuop >= i)
|
|
viewer->menuop = i - 1;
|
|
break;
|
|
}
|
|
|
|
|
|
WriteByte(&m, 0);
|
|
SendBufferToViewer(viewer, m.data, m.cursize, true);
|
|
}
|
|
|
|
static const char dropcmd[] = {svc_stufftext, 'd', 'i', 's', 'c', 'o', 'n', 'n', 'e', 'c', 't', '\n', '\0'};
|
|
|
|
void QW_FreeViewer(cluster_t *cluster, viewer_t *viewer)
|
|
{
|
|
int i;
|
|
//note: unlink them yourself.
|
|
|
|
Sys_Printf(cluster, "Dropping viewer %s\n", viewer->name);
|
|
|
|
//spam them thrice, then forget about them
|
|
Netchan_Transmit(cluster, &viewer->netchan, strlen(dropcmd)+1, dropcmd);
|
|
Netchan_Transmit(cluster, &viewer->netchan, strlen(dropcmd)+1, dropcmd);
|
|
Netchan_Transmit(cluster, &viewer->netchan, strlen(dropcmd)+1, dropcmd);
|
|
|
|
for (i = 0; i < MAX_BACK_BUFFERS; i++)
|
|
{
|
|
if (viewer->backbuf[i].data)
|
|
free(viewer->backbuf[i].data);
|
|
}
|
|
|
|
free(viewer);
|
|
|
|
cluster->numviewers--;
|
|
}
|
|
|
|
void QW_UpdateUDPStuff(cluster_t *cluster)
|
|
{
|
|
char buffer[MAX_MSGLEN*2];
|
|
netadr_t from;
|
|
int fromsize = sizeof(from);
|
|
int read;
|
|
int qport;
|
|
netmsg_t m;
|
|
|
|
viewer_t *v, *f;
|
|
|
|
if (*cluster->master && (cluster->curtime > cluster->mastersendtime || cluster->mastersendtime > cluster->curtime + 4*1000*60)) //urm... time wrapped?
|
|
{
|
|
if (NET_StringToAddr(cluster->master, &from))
|
|
{
|
|
sprintf(buffer, "a\n%i\n0\n", cluster->mastersequence++); //fill buffer with a heartbeat
|
|
//why is there no \xff\xff\xff\xff ?..
|
|
NET_SendPacket(cluster, cluster->qwdsocket, strlen(buffer), buffer, from);
|
|
}
|
|
else
|
|
Sys_Printf(cluster, "Cannot resolve master %s\n", cluster->master);
|
|
|
|
cluster->mastersendtime = cluster->curtime + 3*1000*60; //3 minuites.
|
|
}
|
|
|
|
m.data = buffer;
|
|
m.cursize = 0;
|
|
m.maxsize = MAX_MSGLEN;
|
|
m.readpos = 0;
|
|
|
|
for (;;)
|
|
{
|
|
read = recvfrom(cluster->qwdsocket, buffer, sizeof(buffer), 0, (struct sockaddr*)from, &fromsize);
|
|
|
|
if (read <= 5) //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(cluster, &from, &m);
|
|
continue;
|
|
}
|
|
|
|
//read the qport
|
|
ReadLong(&m);
|
|
ReadLong(&m);
|
|
qport = ReadShort(&m);
|
|
|
|
for (v = cluster->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->server)
|
|
v->maysend = true;
|
|
else if (!v->chokeme || !cluster->chokeonnotupdated)
|
|
{
|
|
v->maysend = true;
|
|
v->chokeme = cluster->chokeonnotupdated;
|
|
}
|
|
|
|
ParseQWC(cluster, v->server, v, &m);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (cluster->viewers && cluster->viewers->drop)
|
|
{
|
|
Sys_Printf(cluster, "Dropping client\n");
|
|
f = cluster->viewers;
|
|
cluster->viewers = f->next;
|
|
|
|
QW_FreeViewer(cluster, f);
|
|
}
|
|
|
|
for (v = cluster->viewers; v; v = v->next)
|
|
{
|
|
if (v->next && v->next->drop)
|
|
{ //free the next/
|
|
Sys_Printf(cluster, "Dropping client\n");
|
|
f = v->next;
|
|
v->next = f->next;
|
|
|
|
QW_FreeViewer(cluster, f);
|
|
}
|
|
|
|
if (v->maysend && (!v->server || !v->server->parsingconnectiondata)) //don't send incompleate connection data.
|
|
{
|
|
v->maysend = false;
|
|
m.cursize = 0;
|
|
if (v->thinksitsconnected)
|
|
{
|
|
SendPlayerStates(v->server, v, &m);
|
|
UpdateStats(v->server, v);
|
|
|
|
if (v->menunum)
|
|
Menu_Draw(cluster, v);
|
|
}
|
|
Netchan_Transmit(cluster, &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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|