engine/fteqtv/parse.c

2074 lines
56 KiB
C
Raw Normal View History

/*
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"
#include "bsd_string.h"
#define ParseError(m) (m)->readpos = (m)->cursize+1 //
static const entity_state_t null_entity_state;
void SendBufferToViewer(viewer_t *v, const char *buffer, int length, qboolean reliable)
{
if (reliable)
{
//try and put it in the normal reliable
if (!v->backbuffered && v->netchan.message.cursize+length < v->netchan.message.maxsize)
WriteData(&v->netchan.message, buffer, length);
else if (v->backbuffered>0 && v->backbuf[v->backbuffered-1].cursize+length < v->backbuf[v->backbuffered-1].maxsize) //try and put it in the current backbuffer
WriteData(&v->backbuf[v->backbuffered-1], buffer, length);
else if (v->backbuffered == MAX_BACK_BUFFERS)
{
v->netchan.message.cursize = 0;
WriteByte(&v->netchan.message, svc_print);
if (!v->netchan.isnqprotocol)
WriteByte(&v->netchan.message, PRINT_HIGH);
WriteString(&v->netchan.message, "backbuffer overflow\n");
if (!v->drop)
Sys_Printf(NULL, "%s backbuffers overflowed\n", v->name); //FIXME
v->drop = true; //we would need too many backbuffers.
}
else
{
//create a new backbuffer
if (!v->backbuf[v->backbuffered].data)
{
InitNetMsg(&v->backbuf[v->backbuffered], (unsigned char *)malloc(MAX_BACKBUF_SIZE), MAX_BACKBUF_SIZE);
}
v->backbuf[v->backbuffered].cursize = 0; //make sure it's empty
WriteData(&v->backbuf[v->backbuffered], buffer, length);
v->backbuffered++;
}
}
}
void Multicast(sv_t *tv, void *buffer, int length, int to, unsigned int playermask, int suitablefor)
{
viewer_t *v;
switch(to)
{
case dem_multiple:
case dem_single:
case dem_stats:
//check and send to them only if they're tracking this player(s).
for (v = tv->cluster->viewers; v; v = v->next)
{
if (v->thinksitsconnected||suitablefor&CONNECTING)
if (v->server == tv)
if (v->trackplayer>=0)
if ((1<<v->trackplayer)&playermask)
{
if (suitablefor&(v->netchan.isnqprotocol?NQ:QW))
SendBufferToViewer(v, buffer, length, true); //FIXME: change the reliable depending on message type
}
}
break;
default:
//send to all
for (v = tv->cluster->viewers; v; v = v->next)
{
if (v->thinksitsconnected||suitablefor&CONNECTING)
if (v->server == tv)
if (suitablefor&(v->netchan.isnqprotocol?NQ:QW))
SendBufferToViewer(v, buffer, length, true); //FIXME: change the reliable depending on message type
}
break;
}
}
void Broadcast(cluster_t *cluster, void *buffer, int length, int suitablefor)
{
viewer_t *v;
for (v = cluster->viewers; v; v = v->next)
{
if (suitablefor&(v->netchan.isnqprotocol?NQ:QW))
SendBufferToViewer(v, buffer, length, true);
}
}
void ConnectionData(sv_t *tv, void *buffer, int length, int to, unsigned int playermask, int suitablefor)
{
if (!tv->parsingconnectiondata)
Multicast(tv, buffer, length, to, playermask, suitablefor);
else if (tv->controller)
{
if (suitablefor&(tv->controller->netchan.isnqprotocol?NQ:QW))
SendBufferToViewer(tv->controller, buffer, length, true);
}
}
static void ParseServerData(sv_t *tv, netmsg_t *m, int to, unsigned int playermask)
{
unsigned int protocol;
unsigned int supported;
viewer_t *v;
//free the old map state
QTV_CleanupMap(tv);
tv->pext1 = 0;
tv->pext2 = 0;
//when it comes to QTV, the proxy 'blindly' forwards the data after parsing the header, so we need to support EVERYTHING the original server might.
//and if we don't, then we might have troubles.
for(;;)
{
protocol = ReadLong(m);
switch (protocol)
{
case PROTOCOL_VERSION:
break;
case PROTOCOL_VERSION_FTE:
protocol = ReadLong(m);
tv->pext1 = protocol;
//HAVE
supported = PEXT_SETVIEW|PEXT_ACCURATETIMINGS; /*simple forwarding*/
supported |= PEXT_256PACKETENTITIES|PEXT_VIEW2|PEXT_HLBSP|PEXT_Q2BSP|PEXT_Q3BSP; //features other than the protocol (stats, simple limits etc)
supported |= PEXT_FLOATCOORDS|PEXT_SPAWNSTATIC2; //working
// supported |= PEXT_CHUNKEDDOWNLOADS; //shouldn't be relevant...
supported |= PEXT_TRANS|PEXT_MODELDBL|PEXT_ENTITYDBL|PEXT_ENTITYDBL2|PEXT_SOUNDDBL;
//replaced by replacementdeltas. we parse these, but we don't actually forward the data right now
supported |= PEXT_SCALE|PEXT_TRANS|PEXT_FATNESS|PEXT_COLOURMOD|PEXT_HEXEN2|PEXT_SETATTACHMENT|PEXT_DPFLAGS;
//stuff that we ought to handle, but don't currently
//PEXT_LIGHTSTYLECOL - woo, fancy rgb colours
//PEXT_CUSTOMTEMPEFFECTS - required for hexen2's effects. kinda messy.
//PEXT_TE_BULLET - implies nq tents too.
//HARD...
//PEXT_CSQC -- all bets are off if we receive a csqc ent update
//totally optional... so will probably never be added...
//PEXT_HULLSIZE - bigger players... maybe. like anyone can depend on this... not supported with mvd players so w/e
//PEXT_CHUNKEDDOWNLOADS - not sure there's much point
//PEXT_SPLITSCREEN - irrelevant for mvds. might be useful as a qw client, but who cares.
//PEXT_SHOWPIC - rare, lame, limited. just yuck.
if (protocol & ~supported)
{
int i;
const char *names[] = {
"PEXT_SETVIEW", "PEXT_SCALE", "PEXT_LIGHTSTYLECOL", "PEXT_TRANS",
"PEXT_VIEW2", "0x00000020", "PEXT_ACCURATETIMINGS", "PEXT_SOUNDDBL",
"PEXT_FATNESS", "PEXT_HLBSP", "PEXT_TE_BULLET", "PEXT_HULLSIZE",
"PEXT_MODELDBL", "PEXT_ENTITYDBL", "PEXT_ENTITYDBL2", "PEXT_FLOATCOORDS",
"0x00010000", "PEXT_Q2BSP", "PEXT_Q3BSP", "PEXT_COLOURMOD",
"PEXT_SPLITSCREEN", "PEXT_HEXEN2", "PEXT_SPAWNSTATIC2", "PEXT_CUSTOMTEMPEFFECTS",
"PEXT_256PACKETENTITIES", "0x02000000", "PEXT_SHOWPIC", "PEXT_SETATTACHMENT",
"0x10000000", "PEXT_CHUNKEDDOWNLOADS","PEXT_CSQC", "PEXT_DPFLAGS",
};
for (i = 0; i < sizeof(names)/sizeof(names[0]); i++)
{
if (protocol & ~supported & (1u<<i))
{
Sys_Printf(tv->cluster, "ParseMessage: PROTOCOL_VERSION_FTE (%s) not supported\n", names[i]);
supported |= (1u<<i);
}
}
if (protocol & ~supported)
Sys_Printf(tv->cluster, "ParseMessage: PROTOCOL_VERSION_FTE (%x) not supported\n", protocol & ~supported);
}
continue;
case PROTOCOL_VERSION_FTE2:
protocol = ReadLong(m);
tv->pext2 = protocol;
supported = 0;
// supported |= PEXT2_PRYDONCURSOR|PEXT2_VOICECHAT|PEXT2_SETANGLEDELTA|PEXT2_REPLACEMENTDELTAS|PEXT2_MAXPLAYERS;
//FIXME: handle the svc and clc if they arrive.
supported |= PEXT2_VOICECHAT;
//WANT
//PEXT2_SETANGLEDELTA
//PEXT2_REPLACEMENTDELTAS
//PEXT2_SETANGLEDELTA
//PEXT2_PREDINFO
//PEXT2_PRYDONCURSOR
if (protocol & ~supported)
Sys_Printf(tv->cluster, "ParseMessage: PROTOCOL_VERSION_FTE2 (%x) not supported\n", protocol & ~supported);
continue;
case PROTOCOL_VERSION_HUFFMAN:
Sys_Printf(tv->cluster, "ParseMessage: PROTOCOL_VERSION_HUFFMAN not supported\n");
ParseError(m);
return;
case PROTOCOL_VERSION_VARLENGTH:
{
int len = ReadLong(m);
if (len < 0 || len > 8192)
{
Sys_Printf(tv->cluster, "ParseMessage: PROTOCOL_VERSION_VARLENGTH invalid\n");
ParseError(m);
return;
}
protocol = ReadLong(m);/*ident*/
switch(protocol)
{
default:
m->readpos += len;
Sys_Printf(tv->cluster, "ParseMessage: PROTOCOL_VERSION_VARLENGTH (%x) not supported\n", protocol);
ParseError(m);
return;
}
}
continue;
case PROTOCOL_VERSION_FRAGMENT:
protocol = ReadLong(m);
Sys_Printf(tv->cluster, "ParseMessage: PROTOCOL_VERSION_FRAGMENT not supported\n");
ParseError(m);
return;
default:
Sys_Printf(tv->cluster, "ParseMessage: Unknown protocol version %x\n", protocol);
ParseError(m);
return;
}
break;
}
tv->mapstarttime = tv->parsetime;
tv->parsingconnectiondata = true;
tv->clservercount = ReadLong(m); //we don't care about server's servercount, it's all reliable data anyway.
tv->map.trackplayer = -1;
ReadString(m, tv->map.gamedir, sizeof(tv->map.gamedir));
#define DEFAULTGAMEDIR "qw"
if (strchr(tv->map.gamedir, ':')) //nuke any multiple gamedirs - we need to read maps which would fail if its not a valid single path.
*strchr(tv->map.gamedir, ';') = 0;
if (!*tv->map.gamedir)
strcpy(tv->map.gamedir, DEFAULTGAMEDIR);
if (!*tv->map.gamedir
|| *tv->map.gamedir == '.'
|| !strcmp(tv->map.gamedir, ".")
|| strstr(tv->map.gamedir, "..")
|| strstr(tv->map.gamedir, "/")
|| strstr(tv->map.gamedir, "\\")
|| strstr(tv->map.gamedir, ":")
)
{
QTV_Printf(tv, "Ignoring unsafe gamedir: \"%s\"\n", tv->map.gamedir);
strcpy(tv->map.gamedir, DEFAULTGAMEDIR);
}
if (tv->usequakeworldprotocols)
tv->map.thisplayer = ReadByte(m)&~128;
else
{
tv->map.thisplayer = MAX_CLIENTS-1;
/*tv->servertime =*/ ReadFloat(m);
}
if (tv->controller)
tv->controller->thisplayer = tv->map.thisplayer;
ReadString(m, tv->map.mapname, sizeof(tv->map.mapname));
QTV_Printf(tv, "Gamedir: %s\n", tv->map.gamedir);
QTV_Printf(tv, "---------------------\n");
Sys_Printf(tv->cluster, "Stream %i: %s\n", tv->streamid, tv->map.mapname);
QTV_Printf(tv, "---------------------\n");
// get the movevars
tv->map.movevars.gravity = ReadFloat(m);
tv->map.movevars.stopspeed = ReadFloat(m);
tv->map.movevars.maxspeed = ReadFloat(m);
tv->map.movevars.spectatormaxspeed = ReadFloat(m);
tv->map.movevars.accelerate = ReadFloat(m);
tv->map.movevars.airaccelerate = ReadFloat(m);
tv->map.movevars.wateraccelerate = ReadFloat(m);
tv->map.movevars.friction = ReadFloat(m);
tv->map.movevars.waterfriction = ReadFloat(m);
tv->map.movevars.entgrav = ReadFloat(m);
for (v = tv->cluster->viewers; v; v = v->next)
{
if (v->server == tv)
v->thinksitsconnected = false;
}
if ((!tv->controller || tv->controller->netchan.isnqprotocol) && tv->usequakeworldprotocols)
{
tv->netchan.message.cursize = 0; //mvdsv sucks
SendClientCommand(tv, "soundlist %i 0\n", tv->clservercount);
}
else
ConnectionData(tv, (void*)((char*)m->data+m->startpos), m->readpos - m->startpos, to, dem_read, QW);
if (tv->controller)
{
QW_ClearViewerState(tv->controller);
tv->controller->trackplayer = tv->map.thisplayer;
}
strcpy(tv->status, "Receiving soundlist\n");
}
/*called if the server changed the map.serverinfo, so we can corrupt it again*/
void QTV_UpdatedServerInfo(sv_t *tv)
{
qboolean fromproxy;
char text[1024];
char value[256];
Info_ValueForKey(tv->map.serverinfo, "*qtv", value, sizeof(value));
if (*value)
{
fromproxy = true;
tv->serverisproxy = fromproxy;
}
else
fromproxy = false;
//add on our extra infos
Info_SetValueForStarKey(tv->map.serverinfo, "*qtv", QTV_VERSION_STRING, sizeof(tv->map.serverinfo));
Info_SetValueForStarKey(tv->map.serverinfo, "*z_ext", Z_EXT_STRING, sizeof(tv->map.serverinfo));
Info_ValueForKey(tv->map.serverinfo, "hostname", tv->map.hostname, sizeof(tv->map.hostname));
//change the hostname (the qtv's hostname with the server's hostname in brackets)
Info_ValueForKey(tv->map.serverinfo, "hostname", value, sizeof(value));
if (fromproxy && strchr(value, '(') && value[strlen(value)-1] == ')') //already has brackets
{ //the fromproxy check is because it's fairly common to find a qw server with brackets after it's name.
char *s;
s = strchr(value, '('); //so strip the parent proxy's hostname, and put our hostname first, leaving the origional server's hostname within the brackets
snprintf(text, sizeof(text), "%s %s", tv->cluster->hostname, s);
}
else
{
if (tv->sourcefile)
snprintf(text, sizeof(text), "%s (recorded from: %s)", tv->cluster->hostname, value);
else
snprintf(text, sizeof(text), "%s (live: %s)", tv->cluster->hostname, value);
}
Info_SetValueForStarKey(tv->map.serverinfo, "hostname", text, sizeof(tv->map.serverinfo));
}
static void ParseCDTrack(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
char nqversion[3];
tv->map.cdtrack = ReadByte(m);
ConnectionData(tv, (void*)((char*)m->data+m->startpos), m->readpos - m->startpos, to, mask, QW);
nqversion[0] = svc_cdtrack;
nqversion[1] = tv->map.cdtrack;
nqversion[2] = tv->map.cdtrack;
ConnectionData(tv, nqversion, 3, to, mask, NQ);
}
static void ParseStufftext(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
viewer_t *v;
char text[1024];
ReadString(m, text, sizeof(text));
// Sys_Printf(tv->cluster, "stuffcmd: %s", text);
if (!strcmp(text, "say proxy:menu\n"))
{ //qizmo's 'previous proxy' message
tv->proxyisselected = true;
if (tv->controller)
QW_SetMenu(tv->controller, MENU_MAIN);
tv->serverisproxy = true; //FIXME: Detect this properly on qizmo
}
else if (!strncmp(text, "//I am a proxy", 14))
tv->serverisproxy = true;
else if (!strncmp(text, "//set prox_inmenu ", 18))
{
if (tv->controller)
QW_SetMenu(tv->controller, atoi(text+18)?MENU_FORWARDING:MENU_NONE);
}
// else if (!strncmp(text, "//set protocolname ", 19))
// else if (!strncmp(text, "//set recorddate ", 17)) //reports when the demo was originally recorded, without needing to depend upon metadata.
// else if (!strncmp(text, "//paknames ", 11))
// else if (!strncmp(text, "//paks ", 7))
// else if (!strncmp(text, "//vwep ", 7))
else if (strstr(text, "screenshot"))
{
if (tv->controller)
{ //let it through to the controller
SendBufferToViewer(tv->controller, (char*)m->data+m->startpos, m->readpos - m->startpos, true);
}
return; //this was generating far too many screenshots when watching demos
}
else if (!strcmp(text, "skins\n"))
{
const char newcmd[10] = {svc_stufftext, 'c', 'm', 'd', ' ', 'n','e','w','\n','\0'};
tv->parsingconnectiondata = false;
strcpy(tv->status, "On server\n");
for (v = tv->cluster->viewers; v; v = v->next)
{
if (v->server == tv && (v != tv->controller || v->netchan.isnqprotocol))
{
v->servercount++;
SendBufferToViewer(v, newcmd, sizeof(newcmd), true);
}
}
if (tv->controller && !tv->controller->netchan.isnqprotocol)
SendBufferToViewer(tv->controller, (char*)m->data+m->startpos, m->readpos - m->startpos, true);
else if (tv->usequakeworldprotocols)
SendClientCommand(tv, "begin %i\n", tv->clservercount);
return;
}
else if (!strncmp(text, "fullserverinfo ", 15))
{
/*strip newline*/
text[strlen(text)-1] = '\0';
/*strip trailing quote*/
text[strlen(text)-1] = '\0';
//copy over the server's serverinfo
strlcpy(tv->map.serverinfo, text+16, sizeof(tv->map.serverinfo));
QTV_UpdatedServerInfo(tv);
if (tv->controller && (tv->controller->netchan.isnqprotocol == false))
SendBufferToViewer(tv->controller, (char*)m->data+m->startpos, m->readpos - m->startpos, true);
return;
}
else if (!strncmp(text, "cmd prespawn ", 13))
{
if (tv->usequakeworldprotocols)
SendClientCommand(tv, "%s", text+4);
return; //commands the game server asked for are pointless.
}
else if (!strncmp(text, "cmd spawn ", 10))
{
if (tv->usequakeworldprotocols)
SendClientCommand(tv, "%s", text+4);
return; //commands the game server asked for are pointless.
}
else if (!strncmp(text, "cmd ", 4))
{
if (tv->controller)
SendBufferToViewer(tv->controller, (char*)m->data+m->startpos, m->readpos - m->startpos, true);
else if (tv->usequakeworldprotocols)
SendClientCommand(tv, "%s", text+4);
return; //commands the game server asked for are pointless.
}
else if (!strncmp(text, "reconnect", 9))
{
if (tv->controller)
SendBufferToViewer(tv->controller, (char*)m->data+m->startpos, m->readpos - m->startpos, true);
else if (tv->usequakeworldprotocols)
SendClientCommand(tv, "new\n");
return;
}
else if (!strncmp(text, "packet ", 7))
{
if (tv->controller)
{ //if we're acting as a proxy, forward the realip packets, and ONLY to the controller
//quakeworld proxies are usually there for routing or protocol advantages, NOT privacy
//(client can always ignore it themselves, but a server might ban you, but at least they'll be less inclined to ban the proxy).
SendBufferToViewer(tv->controller, (char*)m->data+m->startpos, m->readpos - m->startpos, true);
return;
}
if(tv->usequakeworldprotocols)
{//eeeevil hack for proxy-spectating
char *ptr;
char arg[3][ARG_LEN];
netadr_t adr;
ptr = text;
ptr = COM_ParseToken(ptr, arg[0], ARG_LEN, "");
ptr = COM_ParseToken(ptr, arg[1], ARG_LEN, "");
ptr = COM_ParseToken(ptr, arg[2], ARG_LEN, "");
NET_StringToAddr(arg[1], &adr, PROX_DEFAULTSERVERPORT);
Netchan_OutOfBandSocket(tv->cluster, tv->sourcesock, &adr, strlen(arg[2]), arg[2]);
//this is an evil hack
SendClientCommand(tv, "new\n");
return;
}
Sys_Printf(tv->cluster, "packet stuffcmd in an mvd\n"); //shouldn't ever happen, try ignoring it.
return;
}
else if (tv->usequakeworldprotocols && !strncmp(text, "setinfo ", 8))
{
Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, Q1);
if (!tv->controller)
SendClientCommand(tv, "%s", text);
}
else
{
Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, Q1);
return;
}
}
static void ParseSetInfo(sv_t *tv, netmsg_t *m)
{
int pnum;
char key[64];
char value[256];
pnum = ReadByte(m);
ReadString(m, key, sizeof(key));
ReadString(m, value, sizeof(value));
if (pnum < MAX_CLIENTS)
Info_SetValueForStarKey(tv->map.players[pnum].userinfo, key, value, sizeof(tv->map.players[pnum].userinfo));
ConnectionData(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, dem_all, (unsigned)-1, QW);
}
static void ParseServerinfo(sv_t *tv, netmsg_t *m)
{
char key[64];
char value[256];
ReadString(m, key, sizeof(key));
ReadString(m, value, sizeof(value));
if (strcmp(key, "hostname")) //don't allow the hostname to change, but allow the server to change other serverinfos.
Info_SetValueForStarKey(tv->map.serverinfo, key, value, sizeof(tv->map.serverinfo));
ConnectionData(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, dem_all, (unsigned)-1, QW);
}
static void ParsePrint(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
char text[1024];
char nqbuffer[1024];
int level;
level = ReadByte(m);
ReadString(m, text, sizeof(text)-2);
if (level == 3)
{
//FIXME: that number shouldn't be hard-coded
if (!strncmp(text, "#0:qtv_say:#", 12) || !strncmp(text, "#0:qtv_say_game:#", 17) || !strncmp(text, "#0:qtv_say_team_game:#", 22))
{
char *colon;
colon = strchr(text, ':');
colon = strchr(colon+1, ':');
colon = strchr(colon+1, ':');
if (colon)
{
//de-fuck qqshka's extra gibberish.
snprintf(nqbuffer, sizeof(nqbuffer), "%c%c[QTV]%s\n", svc_print, 3, colon+1);
Multicast(tv, nqbuffer, strlen(nqbuffer)+1, to, mask, QW|CONNECTING);
snprintf(nqbuffer, sizeof(nqbuffer), "%c%c[QTV]%s\n", svc_print, 1, colon+1);
Multicast(tv, nqbuffer, strlen(nqbuffer)+1, to, mask, NQ|CONNECTING);
return;
}
}
strlcpy(nqbuffer+2, text, sizeof(nqbuffer)-2);
nqbuffer[1] = 1; //nq chat is prefixed with a 1
}
else
{
strlcpy(nqbuffer+1, text, sizeof(nqbuffer)-1);
}
nqbuffer[0] = svc_print;
if ((to&dem_mask) == dem_all || to == dem_read)
{
if (level > 1)
{
QTV_Printf(tv, "%s", text);
}
}
Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, QW|CONNECTING);
Multicast(tv, nqbuffer, strlen(nqbuffer)+1, to, mask, NQ|CONNECTING);
}
static void ParseCenterprint(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
viewer_t *v;
char text[1024];
ReadString(m, text, sizeof(text));
switch(to)
{
case dem_multiple:
case dem_single:
case dem_stats:
//check and send to them only if they're tracking this player(s).
for (v = tv->cluster->viewers; v; v = v->next)
{
if (!v->menunum || v->menunum == MENU_FORWARDING)
if (v->thinksitsconnected)
if (v->server == tv)
if (v->trackplayer>=0)
if ((1<<v->trackplayer)&mask)
{
SendBufferToViewer(v, (char*)m->data+m->startpos, m->readpos - m->startpos, true); //FIXME: change the reliable depending on message type
}
}
break;
default:
//send to all
for (v = tv->cluster->viewers; v; v = v->next)
{
if (!v->menunum || v->menunum == MENU_FORWARDING)
if (v->thinksitsconnected)
if (v->server == tv)
SendBufferToViewer(v, (char*)m->data+m->startpos, m->readpos - m->startpos, true); //FIXME: change the reliable depending on message type
}
break;
}
}
static int ParseList(sv_t *tv, netmsg_t *m, filename_t *list, int to, unsigned int mask, qboolean big)
{
int first;
if (big)
first = ReadShort(m)+1;
else
first = ReadByte(m)+1;
for (; first < MAX_LIST; first++)
{
ReadString(m, list[first].name, sizeof(list[first].name));
// printf("read %i: %s\n", first, list[first].name);
if (!*list[first].name)
break;
// printf("%i: %s\n", first, list[first].name);
}
return ReadByte(m);
}
dpp7: Treat 'dropped' c2s packets as choked when using dpp7 protocols. This is because the protocol provides no way to disambiguate, and I don't like false reports of packetloss (only reliables loss can be detected, and that's not frequent enough to be meaningful). Pings can still be determined with dpp7, for those few packets which are acked. package manager: reworked to enable/disable plugins when downloaded, which can also be present-but-disabled. package manager: display a confirmation prompt before applying changes. do not allow other changes to be made while applying. prompt may be skipped with 'pkg apply' in dedicated servers. sv: downloads are no longer forced to lower case. sv: added sv_demoAutoCompress cvar. set to 1 to directly record to *.mvd.gz cl: properly support directly playing .mvd.gz files menus: reworked to separate mouse and keyboard focus. mouse focus becomes keyboard focus only on mouse clicks. tooltips follow mouse cursors. menus: cleaned up menu heirachy a little. now simpler. server browser: changed 'hide *' filters to 'show *' instead. I felt it was more logical. deluxmapping: changed to disabled, load, generate, like r_loadlit is. render targets api now supports negative formats to mean nearest filtering, where filtering is part of texture state. drawrotpic fixed, now batches and interacts with drawpic correctly. drawline fixed, no interacts with draw* correctly, but still does not batch. fixed saving games. provide proper userinfo to nq clients, where supported. qcc: catch string table overflows safely, giving errors instead of crashes. switch to 32bit statements if some over-sized function requires it. qtv: some bigcoords support tweaks git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5073 fc73d0e0-1445-4013-8a0c-d673dee63da5
2017-03-20 22:27:07 -07:00
static void ParseEntityState(sv_t *tv, entity_state_t *es, netmsg_t *m) //for baselines/static entities
{
int i;
es->modelindex = ReadByte(m);
es->frame = ReadByte(m);
es->colormap = ReadByte(m);
es->skinnum = ReadByte(m);
for (i = 0; i < 3; i++)
{
es->origin[i] = ReadCoord(m, tv->pext1);
es->angles[i] = ReadAngle(m, tv->pext1);
}
}
static void ParseStaticSound(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
if (tv->map.staticsound_count == MAX_STATICSOUNDS)
{
tv->map.staticsound_count--; // don't be fatal.
Sys_Printf(tv->cluster, "Too many static sounds\n");
}
tv->map.staticsound[tv->map.staticsound_count].origin[0] = ReadCoord(m, tv->pext1);
tv->map.staticsound[tv->map.staticsound_count].origin[1] = ReadCoord(m, tv->pext1);
tv->map.staticsound[tv->map.staticsound_count].origin[2] = ReadCoord(m, tv->pext1);
tv->map.staticsound[tv->map.staticsound_count].soundindex = ReadByte(m);
tv->map.staticsound[tv->map.staticsound_count].volume = ReadByte(m);
tv->map.staticsound[tv->map.staticsound_count].attenuation = ReadByte(m);
tv->map.staticsound_count++;
ConnectionData(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, Q1);
}
static void ParseIntermission(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
ReadShort(m);
ReadShort(m);
ReadShort(m);
ReadByte(m);
ReadByte(m);
ReadByte(m);
Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, QW);
}
extern const usercmd_t nullcmd;
static void ParsePlayerInfo(sv_t *tv, netmsg_t *m, qboolean clearoldplayers)
{
usercmd_t nonnullcmd;
int flags;
int num;
int i;
if (clearoldplayers)
{
for (i = 0; i < MAX_CLIENTS; i++)
{ //hide players
//they'll be sent after this packet.
tv->map.players[i].oldactive = tv->map.players[i].active;
tv->map.players[i].active = false;
}
}
num = ReadByte(m);
if (num >= MAX_CLIENTS)
{
num = 0; // don't be fatal.
Sys_Printf(tv->cluster, "Too many svc_playerinfos, wrapping\n");
}
tv->map.players[num].old = tv->map.players[num].current;
if (tv->usequakeworldprotocols)
{
flags = (unsigned short)ReadShort (m);
tv->map.players[num].current.origin[0] = ReadCoord (m, tv->pext1);
tv->map.players[num].current.origin[1] = ReadCoord (m, tv->pext1);
tv->map.players[num].current.origin[2] = ReadCoord (m, tv->pext1);
tv->map.players[num].current.frame = ReadByte(m);
if (flags & PF_MSEC)
ReadByte (m);
if (flags & PF_COMMAND)
{
ReadDeltaUsercmd(m, &nullcmd, &nonnullcmd);
tv->map.players[num].current.angles[0] = nonnullcmd.angles[0];
tv->map.players[num].current.angles[1] = nonnullcmd.angles[1];
tv->map.players[num].current.angles[2] = nonnullcmd.angles[2];
}
else
{ //the only reason we'd not get a command is if it's us.
if (tv->controller)
{
tv->map.players[num].current.angles[0] = tv->controller->ucmds[2].angles[0];
tv->map.players[num].current.angles[1] = tv->controller->ucmds[2].angles[1];
tv->map.players[num].current.angles[2] = tv->controller->ucmds[2].angles[2];
}
else
{
tv->map.players[num].current.angles[0] = tv->proxyplayerangles[0];
tv->map.players[num].current.angles[1] = tv->proxyplayerangles[1];
tv->map.players[num].current.angles[2] = tv->proxyplayerangles[2];
}
}
for (i=0 ; i<3 ; i++)
{
if (flags & (PF_VELOCITY1<<i) )
tv->map.players[num].current.velocity[i] = ReadShort(m);
else
tv->map.players[num].current.velocity[i] = 0;
}
tv->map.players[num].gibbed = !!(flags & PF_GIB);
tv->map.players[num].dead = !!(flags & PF_DEAD);
if (flags & PF_MODEL)
tv->map.players[num].current.modelindex = ReadByte (m);
else
tv->map.players[num].current.modelindex = tv->map.modelindex_player;
if (flags & PF_SKINNUM)
tv->map.players[num].current.skinnum = ReadByte (m);
else
tv->map.players[num].current.skinnum = 0;
if (flags & PF_EFFECTS)
tv->map.players[num].current.effects = ReadByte (m);
else
tv->map.players[num].current.effects = 0;
if (flags & PF_WEAPONFRAME)
tv->map.players[num].current.weaponframe = ReadByte (m);
else
tv->map.players[num].current.weaponframe = 0;
tv->map.players[num].active = true;
}
else
{
flags = ReadShort(m);
tv->map.players[num].gibbed = !!(flags & DF_GIB);
tv->map.players[num].dead = !!(flags & DF_DEAD);
tv->map.players[num].current.frame = ReadByte(m);
for (i = 0; i < 3; i++)
{
if (flags & (DF_ORIGIN << i))
tv->map.players[num].current.origin[i] = ReadCoord (m, tv->pext1);
}
for (i = 0; i < 3; i++)
{
if (flags & (DF_ANGLES << i))
{
tv->map.players[num].current.angles[i] = (ReadShort(m)/(float)0x10000)*360;
}
}
if (flags & DF_MODEL)
tv->map.players[num].current.modelindex = ReadByte (m);
if (flags & DF_SKINNUM)
tv->map.players[num].current.skinnum = ReadByte (m);
if (flags & DF_EFFECTS)
tv->map.players[num].current.effects = ReadByte (m);
if (flags & DF_WEAPONFRAME)
tv->map.players[num].current.weaponframe = ReadByte (m);
tv->map.players[num].active = true;
}
tv->map.players[num].leafcount = BSP_SphereLeafNums(tv->map.bsp, MAX_ENTITY_LEAFS, tv->map.players[num].leafs,
dpp7: Treat 'dropped' c2s packets as choked when using dpp7 protocols. This is because the protocol provides no way to disambiguate, and I don't like false reports of packetloss (only reliables loss can be detected, and that's not frequent enough to be meaningful). Pings can still be determined with dpp7, for those few packets which are acked. package manager: reworked to enable/disable plugins when downloaded, which can also be present-but-disabled. package manager: display a confirmation prompt before applying changes. do not allow other changes to be made while applying. prompt may be skipped with 'pkg apply' in dedicated servers. sv: downloads are no longer forced to lower case. sv: added sv_demoAutoCompress cvar. set to 1 to directly record to *.mvd.gz cl: properly support directly playing .mvd.gz files menus: reworked to separate mouse and keyboard focus. mouse focus becomes keyboard focus only on mouse clicks. tooltips follow mouse cursors. menus: cleaned up menu heirachy a little. now simpler. server browser: changed 'hide *' filters to 'show *' instead. I felt it was more logical. deluxmapping: changed to disabled, load, generate, like r_loadlit is. render targets api now supports negative formats to mean nearest filtering, where filtering is part of texture state. drawrotpic fixed, now batches and interacts with drawpic correctly. drawline fixed, no interacts with draw* correctly, but still does not batch. fixed saving games. provide proper userinfo to nq clients, where supported. qcc: catch string table overflows safely, giving errors instead of crashes. switch to 32bit statements if some over-sized function requires it. qtv: some bigcoords support tweaks git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5073 fc73d0e0-1445-4013-8a0c-d673dee63da5
2017-03-20 22:27:07 -07:00
tv->map.players[num].current.origin[0],
tv->map.players[num].current.origin[1],
tv->map.players[num].current.origin[2], 32);
}
static int readentitynum(netmsg_t *m, unsigned int *retflags)
{
int entnum;
unsigned int flags;
flags = ReadShort(m);
if (!flags)
{
*retflags = 0;
return 0;
}
entnum = flags&511;
flags &= ~511;
if (flags & U_MOREBITS)
{
flags |= ReadByte(m);
if (flags & UX_EVENMORE)
flags |= ReadByte(m)<<16;
if (flags & UX_YETMORE)
flags |= ReadByte(m)<<24;
}
if (flags & UX_ENTITYDBL)
entnum += 512;
if (flags & UX_ENTITYDBL2)
entnum += 1024;
*retflags = flags;
return entnum;
}
static void ParseEntityDelta(sv_t *tv, netmsg_t *m, const entity_state_t *old, entity_state_t *new, unsigned int flags, entity_t *ent, qboolean forcerelink)
{
memcpy(new, old, sizeof(entity_state_t));
if (flags & U_MODEL)
{
if (flags & UX_MODELDBL)
new->modelindex = ReadByte(m)|0x100; //doubled limit...
else
new->modelindex = ReadByte(m);
}
else if (flags & UX_MODELDBL)
new->modelindex = ReadShort(m); //more sane path...
if (flags & U_FRAME)
new->frame = ReadByte(m);
if (flags & U_COLORMAP)
new->colormap = ReadByte(m);
if (flags & U_SKIN)
new->skinnum = ReadByte(m);
if (flags & U_EFFECTS)
new->effects = (new->effects&0xff00)|ReadByte(m);
if (flags & U_ORIGIN1)
new->origin[0] = ReadCoord(m, tv->pext1);
if (flags & U_ANGLE1)
new->angles[0] = ReadAngle(m, tv->pext1);
if (flags & U_ORIGIN2)
new->origin[1] = ReadCoord(m, tv->pext1);
if (flags & U_ANGLE2)
new->angles[1] = ReadAngle(m, tv->pext1);
if (flags & U_ORIGIN3)
new->origin[2] = ReadCoord(m, tv->pext1);
if (flags & U_ANGLE3)
new->angles[2] = ReadAngle(m, tv->pext1);
if (flags & UX_SCALE)
new->scale = ReadByte(m);
if (flags & UX_ALPHA)
new->alpha = ReadByte(m);
if (flags & UX_FATNESS)
/*new->fatness = (signed char)*/ReadByte(m);
if (flags & UX_DRAWFLAGS)
/*new->hexen2flags =*/ ReadByte(m);
if (flags & UX_ABSLIGHT)
/*new->abslight =*/ ReadByte(m);
if (flags & UX_COLOURMOD)
{
/*new->colormod[0] =*/ ReadByte(m);
/*new->colormod[1] =*/ ReadByte(m);
/*new->colormod[2] =*/ ReadByte(m);
}
if (flags & UX_DPFLAGS)
{ // these are bits for the 'flags' field of the entity_state_t
/*new->dpflags =*/ ReadByte(m);
}
if (flags & UX_TAGINFO)
{
/*new->tagentity =*/ ReadShort(m);
/*new->tagindex =*/ ReadShort(m);
}
if (flags & UX_LIGHT)
{
/*new->light[0] =*/ ReadShort(m);
/*new->light[1] =*/ ReadShort(m);
/*new->light[2] =*/ ReadShort(m);
/*new->light[3] =*/ ReadShort(m);
/*new->lightstyle =*/ ReadByte(m);
/*new->lightpflags =*/ ReadByte(m);
}
if (flags & UX_EFFECTS16)
new->effects = (new->effects&0x00ff)|(ReadByte(m)<<8);
if (forcerelink || (flags & (U_ORIGIN1|U_ORIGIN2|U_ORIGIN3|U_MODEL)))
{
if (ent)
ent->leafcount =
BSP_SphereLeafNums(tv->map.bsp, MAX_ENTITY_LEAFS, ent->leafs,
new->origin[0],
new->origin[1],
new->origin[2], 32);
}
}
static int ExpandFrame(unsigned int newmax, frame_t *frame)
{
entity_state_t *newents;
unsigned short *newnums;
if (newmax < frame->maxents)
return true;
newmax += 16;
newents = malloc(sizeof(*newents) * newmax);
if (!newents)
return false;
newnums = malloc(sizeof(*newnums) * newmax);
if (!newnums)
{
free(newents);
return false;
}
memcpy(newents, frame->ents, sizeof(*newents) * frame->maxents);
memcpy(newnums, frame->entnums, sizeof(*newnums) * frame->maxents);
if (frame->ents)
free(frame->ents);
if (frame->entnums)
free(frame->entnums);
frame->ents = newents;
frame->entnums = newnums;
frame->maxents = newmax;
return true;
}
static void ParsePacketEntities(sv_t *tv, netmsg_t *m, int deltaframe)
{
frame_t *newframe;
frame_t *oldframe;
int oldcount;
int newnum, oldnum;
int newindex, oldindex;
unsigned int flags;
viewer_t *v;
tv->map.nailcount = 0;
tv->physicstime = tv->curtime;
if (tv->cluster->chokeonnotupdated)
{
for (v = tv->cluster->viewers; v; v = v->next)
{
if (v->server == tv)
v->chokeme = false;
}
for (v = tv->cluster->viewers; v; v = v->next)
{
if (v->server == tv && v->netchan.isnqprotocol)
v->maysend = true;
}
}
if (deltaframe != -1)
deltaframe &= (ENTITY_FRAMES-1);
if (tv->usequakeworldprotocols)
{
newframe = &tv->map.frame[tv->netchan.incoming_sequence & (ENTITY_FRAMES-1)];
if (tv->netchan.outgoing_sequence - tv->netchan.incoming_sequence >= ENTITY_FRAMES - 1)
{
//should drop it
Sys_Printf(tv->cluster, "Outdated frames\n");
}
else if (deltaframe != -1 && newframe->oldframe != deltaframe)
Sys_Printf(tv->cluster, "Mismatching delta frames\n");
}
else
{
deltaframe = tv->netchan.incoming_sequence & (ENTITY_FRAMES-1);
tv->netchan.incoming_sequence++;
newframe = &tv->map.frame[tv->netchan.incoming_sequence & (ENTITY_FRAMES-1)];
}
if (deltaframe != -1)
{
oldframe = &tv->map.frame[deltaframe];
oldcount = oldframe->numents;
}
else
{
oldframe = NULL;
oldcount = 0;
}
oldindex = 0;
newindex = 0;
//printf("frame\n");
for(;;)
{
newnum = readentitynum(m, &flags);
if (!newnum)
{
//end of packet
//any remaining old ents need to be copied to the new frame
while (oldindex < oldcount)
{
//printf("Propogate (spare)\n");
if (!ExpandFrame(newindex, newframe))
break;
memcpy(&newframe->ents[newindex], &oldframe->ents[oldindex], sizeof(entity_state_t));
newframe->entnums[newindex] = oldframe->entnums[oldindex];
newindex++;
oldindex++;
}
break;
}
if (oldindex >= oldcount)
oldnum = 0xffff;
else
oldnum = oldframe->entnums[oldindex];
while(newnum > oldnum)
{
//printf("Propogate (unchanged)\n");
if (!ExpandFrame(newindex, newframe))
break;
memcpy(&newframe->ents[newindex], &oldframe->ents[oldindex], sizeof(entity_state_t));
newframe->entnums[newindex] = oldframe->entnums[oldindex];
newindex++;
oldindex++;
if (oldindex >= oldcount)
oldnum = 0xffff;
else
oldnum = oldframe->entnums[oldindex];
}
if (newnum < oldnum)
{ //this ent wasn't in the last packet
//printf("add\n");
if (flags & U_REMOVE)
{ //remove this ent... just don't copy it across.
//printf("add\n");
continue;
}
if (!ExpandFrame(newindex, newframe))
break;
ParseEntityDelta(tv, m, &tv->map.entity[newnum].baseline, &newframe->ents[newindex], flags, &tv->map.entity[newnum], true);
newframe->entnums[newindex] = newnum;
newindex++;
}
else if (newnum == oldnum)
{
if (flags & U_REMOVE)
{ //remove this ent... just don't copy it across.
//printf("add\n");
oldindex++;
continue;
}
//printf("Propogate (changed)\n");
if (!ExpandFrame(newindex, newframe))
break;
ParseEntityDelta(tv, m, &oldframe->ents[oldindex], &newframe->ents[newindex], flags, &tv->map.entity[newnum], false);
newframe->entnums[newindex] = newnum;
newindex++;
oldindex++;
}
}
newframe->numents = newindex;
return;
/*
//luckilly, only updated entities are here, so that keeps cpu time down a bit.
for (;;)
{
flags = ReadShort(m);
if (!flags)
break;
entnum = flags & 511;
if (tv->maxents < entnum)
tv->maxents = entnum;
flags &= ~511;
memcpy(&tv->entity[entnum].old, &tv->entity[entnum].current, sizeof(entity_state_t)); //ow.
if (flags & U_REMOVE)
{
tv->entity[entnum].current.modelindex = 0;
continue;
}
if (!tv->entity[entnum].current.modelindex) //lerp from baseline
{
memcpy(&tv->entity[entnum].current, &tv->entity[entnum].baseline, sizeof(entity_state_t));
forcerelink = true;
}
else
forcerelink = false;
if (flags & U_MOREBITS)
flags |= ReadByte(m);
if (flags & U_MODEL)
tv->entity[entnum].current.modelindex = ReadByte(m);
if (flags & U_FRAME)
tv->entity[entnum].current.frame = ReadByte(m);
if (flags & U_COLORMAP)
tv->entity[entnum].current.colormap = ReadByte(m);
if (flags & U_SKIN)
tv->entity[entnum].current.skinnum = ReadByte(m);
if (flags & U_EFFECTS)
tv->entity[entnum].current.effects = ReadByte(m);
if (flags & U_ORIGIN1)
tv->entity[entnum].current.origin[0] = ReadShort(m);
if (flags & U_ANGLE1)
tv->entity[entnum].current.angles[0] = ReadByte(m);
if (flags & U_ORIGIN2)
tv->entity[entnum].current.origin[1] = ReadShort(m);
if (flags & U_ANGLE2)
tv->entity[entnum].current.angles[1] = ReadByte(m);
if (flags & U_ORIGIN3)
tv->entity[entnum].current.origin[2] = ReadShort(m);
if (flags & U_ANGLE3)
tv->entity[entnum].current.angles[2] = ReadByte(m);
tv->entity[entnum].updatetime = tv->curtime;
if (!tv->entity[entnum].old.modelindex) //no old state
memcpy(&tv->entity[entnum].old, &tv->entity[entnum].current, sizeof(entity_state_t)); //copy the new to the old, so we don't end up with interpolation glitches
if ((flags & (U_ORIGIN1 | U_ORIGIN2 | U_ORIGIN3)) || forcerelink)
tv->entity[entnum].leafcount = BSP_SphereLeafNums(tv->bsp, MAX_ENTITY_LEAFS, tv->entity[entnum].leafs,
dpp7: Treat 'dropped' c2s packets as choked when using dpp7 protocols. This is because the protocol provides no way to disambiguate, and I don't like false reports of packetloss (only reliables loss can be detected, and that's not frequent enough to be meaningful). Pings can still be determined with dpp7, for those few packets which are acked. package manager: reworked to enable/disable plugins when downloaded, which can also be present-but-disabled. package manager: display a confirmation prompt before applying changes. do not allow other changes to be made while applying. prompt may be skipped with 'pkg apply' in dedicated servers. sv: downloads are no longer forced to lower case. sv: added sv_demoAutoCompress cvar. set to 1 to directly record to *.mvd.gz cl: properly support directly playing .mvd.gz files menus: reworked to separate mouse and keyboard focus. mouse focus becomes keyboard focus only on mouse clicks. tooltips follow mouse cursors. menus: cleaned up menu heirachy a little. now simpler. server browser: changed 'hide *' filters to 'show *' instead. I felt it was more logical. deluxmapping: changed to disabled, load, generate, like r_loadlit is. render targets api now supports negative formats to mean nearest filtering, where filtering is part of texture state. drawrotpic fixed, now batches and interacts with drawpic correctly. drawline fixed, no interacts with draw* correctly, but still does not batch. fixed saving games. provide proper userinfo to nq clients, where supported. qcc: catch string table overflows safely, giving errors instead of crashes. switch to 32bit statements if some over-sized function requires it. qtv: some bigcoords support tweaks git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5073 fc73d0e0-1445-4013-8a0c-d673dee63da5
2017-03-20 22:27:07 -07:00
tv->entity[entnum].current.origin[0],
tv->entity[entnum].current.origin[1],
tv->entity[entnum].current.origin[2], 32);
}
*/
}
void ParseSpawnStatic(sv_t *tv, netmsg_t *m, int to, unsigned int mask, qboolean delta)
{
if (tv->map.spawnstatic_count == MAX_STATICENTITIES)
{
tv->map.spawnstatic_count--; // don't be fatal.
Sys_Printf(tv->cluster, "Too many static entities\n");
}
if (delta)
{
unsigned int flags;
readentitynum(m, &flags);
ParseEntityDelta(tv, m, &null_entity_state, &tv->map.spawnstatic[tv->map.spawnstatic_count], flags, NULL, false);
}
else
ParseEntityState(tv, &tv->map.spawnstatic[tv->map.spawnstatic_count], m);
tv->map.spawnstatic_count++;
ConnectionData(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, Q1);
}
static void ParseBaseline(sv_t *tv, netmsg_t *m, int to, unsigned int mask, qboolean delta)
{
unsigned int entnum;
if (delta)
{
entity_state_t es;
unsigned int flags;
entnum = readentitynum(m, &flags);
ParseEntityDelta(tv, m, &null_entity_state, &es, flags, NULL, false);
if (entnum >= MAX_ENTITIES)
{
ParseError(m);
return;
}
tv->map.entity[entnum].baseline = es;
}
else
{
entnum = ReadShort(m);
if (entnum >= MAX_ENTITIES)
{
ParseError(m);
return;
}
ParseEntityState(tv, &tv->map.entity[entnum].baseline, m);
}
ConnectionData(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, Q1);
}
static void ParseUpdatePing(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
int pnum;
int ping;
pnum = ReadByte(m);
ping = ReadShort(m);
if (pnum < MAX_CLIENTS)
tv->map.players[pnum].ping = ping;
else
Sys_Printf(tv->cluster, "svc_updateping: invalid player number\n");
Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, QW);
}
static void ParseUpdateFrags(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
int pnum;
int frags;
pnum = ReadByte(m);
frags = (signed short)ReadShort(m);
if (pnum < MAX_CLIENTS)
tv->map.players[pnum].frags = frags;
else
Sys_Printf(tv->cluster, "svc_updatefrags: invalid player number\n");
Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, (pnum < 16)?Q1:QW);
}
static void ParseUpdateStat(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
unsigned int pnum;
int value;
int statnum;
statnum = ReadByte(m);
value = ReadByte(m);
if (statnum < MAX_STATS)
{
for (pnum = 0; pnum < MAX_CLIENTS; pnum++)
{
if (mask & (1<<pnum))
tv->map.players[pnum].stats[statnum] = value;
}
}
else
Sys_Printf(tv->cluster, "svc_updatestat: invalid stat number\n");
// Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, QW);
}
static void ParseUpdateStatLong(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
unsigned int pnum;
int value;
int statnum;
statnum = ReadByte(m);
value = ReadLong(m);
if (statnum < MAX_STATS)
{
for (pnum = 0; pnum < MAX_CLIENTS; pnum++)
{
if (mask & (1<<pnum))
tv->map.players[pnum].stats[statnum] = value;
}
}
else
Sys_Printf(tv->cluster, "svc_updatestatlong: invalid stat number\n");
// Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, QW);
}
static void ParseUpdateUserinfo(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
int pnum;
pnum = ReadByte(m);
ReadLong(m);
if (pnum < MAX_CLIENTS)
ReadString(m, tv->map.players[pnum].userinfo, sizeof(tv->map.players[pnum].userinfo));
else
{
Sys_Printf(tv->cluster, "svc_updateuserinfo: invalid player number\n");
while (ReadByte(m)) //suck out the message.
{
}
}
ConnectionData(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, QW);
}
static void ParsePacketloss(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
unsigned int pnum;
int value;
pnum = ReadByte(m)%MAX_CLIENTS;
value = ReadByte(m);
if (pnum < MAX_CLIENTS)
tv->map.players[pnum].packetloss = value;
else
Sys_Printf(tv->cluster, "svc_updatepl: invalid player number\n");
Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, QW);
}
static void ParseUpdateEnterTime(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
unsigned int pnum;
float value;
pnum = ReadByte(m)%MAX_CLIENTS;
value = ReadFloat(m);
if (pnum < MAX_CLIENTS)
tv->map.players[pnum].entertime = value;
else
Sys_Printf(tv->cluster, "svc_updateentertime: invalid player number\n");
ConnectionData(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, QW);
}
static void ParseSound(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
#define SND_VOLUME (1<<15) // a qbyte
#define SND_ATTENUATION (1<<14) // a qbyte
#define DEFAULT_SOUND_PACKET_VOLUME 255
#define DEFAULT_SOUND_PACKET_ATTENUATION 1.0
int i;
int channel;
unsigned char vol;
unsigned char atten;
unsigned char sound_num;
float org[3];
int ent;
netmsg_t nqversion;
unsigned char nqbuffer[64];
InitNetMsg(&nqversion, nqbuffer, sizeof(nqbuffer));
channel = (unsigned short)ReadShort(m);
dpp7: Treat 'dropped' c2s packets as choked when using dpp7 protocols. This is because the protocol provides no way to disambiguate, and I don't like false reports of packetloss (only reliables loss can be detected, and that's not frequent enough to be meaningful). Pings can still be determined with dpp7, for those few packets which are acked. package manager: reworked to enable/disable plugins when downloaded, which can also be present-but-disabled. package manager: display a confirmation prompt before applying changes. do not allow other changes to be made while applying. prompt may be skipped with 'pkg apply' in dedicated servers. sv: downloads are no longer forced to lower case. sv: added sv_demoAutoCompress cvar. set to 1 to directly record to *.mvd.gz cl: properly support directly playing .mvd.gz files menus: reworked to separate mouse and keyboard focus. mouse focus becomes keyboard focus only on mouse clicks. tooltips follow mouse cursors. menus: cleaned up menu heirachy a little. now simpler. server browser: changed 'hide *' filters to 'show *' instead. I felt it was more logical. deluxmapping: changed to disabled, load, generate, like r_loadlit is. render targets api now supports negative formats to mean nearest filtering, where filtering is part of texture state. drawrotpic fixed, now batches and interacts with drawpic correctly. drawline fixed, no interacts with draw* correctly, but still does not batch. fixed saving games. provide proper userinfo to nq clients, where supported. qcc: catch string table overflows safely, giving errors instead of crashes. switch to 32bit statements if some over-sized function requires it. qtv: some bigcoords support tweaks git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5073 fc73d0e0-1445-4013-8a0c-d673dee63da5
2017-03-20 22:27:07 -07:00
if (channel & SND_VOLUME)
vol = ReadByte (m);
else
vol = DEFAULT_SOUND_PACKET_VOLUME;
dpp7: Treat 'dropped' c2s packets as choked when using dpp7 protocols. This is because the protocol provides no way to disambiguate, and I don't like false reports of packetloss (only reliables loss can be detected, and that's not frequent enough to be meaningful). Pings can still be determined with dpp7, for those few packets which are acked. package manager: reworked to enable/disable plugins when downloaded, which can also be present-but-disabled. package manager: display a confirmation prompt before applying changes. do not allow other changes to be made while applying. prompt may be skipped with 'pkg apply' in dedicated servers. sv: downloads are no longer forced to lower case. sv: added sv_demoAutoCompress cvar. set to 1 to directly record to *.mvd.gz cl: properly support directly playing .mvd.gz files menus: reworked to separate mouse and keyboard focus. mouse focus becomes keyboard focus only on mouse clicks. tooltips follow mouse cursors. menus: cleaned up menu heirachy a little. now simpler. server browser: changed 'hide *' filters to 'show *' instead. I felt it was more logical. deluxmapping: changed to disabled, load, generate, like r_loadlit is. render targets api now supports negative formats to mean nearest filtering, where filtering is part of texture state. drawrotpic fixed, now batches and interacts with drawpic correctly. drawline fixed, no interacts with draw* correctly, but still does not batch. fixed saving games. provide proper userinfo to nq clients, where supported. qcc: catch string table overflows safely, giving errors instead of crashes. switch to 32bit statements if some over-sized function requires it. qtv: some bigcoords support tweaks git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5073 fc73d0e0-1445-4013-8a0c-d673dee63da5
2017-03-20 22:27:07 -07:00
if (channel & SND_ATTENUATION)
atten = ReadByte (m) / 64.0;
else
atten = DEFAULT_SOUND_PACKET_ATTENUATION;
sound_num = ReadByte (m);
ent = (channel>>3)&1023;
channel &= 7;
for (i=0 ; i<3 ; i++)
org[i] = ReadCoord (m, tv->pext1);
Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, QW);
WriteByte(&nqversion, svc_sound);
i = 0;
if (vol != DEFAULT_SOUND_PACKET_VOLUME)
i |= 1;
if (atten != DEFAULT_SOUND_PACKET_ATTENUATION)
i |= 2;
if (ent > 8191 || channel > 7)
i |= 8;
if (sound_num > 255)
i |= 16;
WriteByte(&nqversion, i);
if (i & 1)
WriteByte(&nqversion, vol);
if (i & 2)
WriteByte(&nqversion, atten*64);
if (i & 8)
{
WriteShort(&nqversion, ent);
WriteByte(&nqversion, channel);
}
else
WriteShort(&nqversion, (ent<<3) | channel);
if (i & 16)
WriteShort(&nqversion, sound_num);
else
WriteByte(&nqversion, sound_num);
WriteCoord(&nqversion, org[0], tv->pext1);
WriteCoord(&nqversion, org[1], tv->pext1);
WriteCoord(&nqversion, org[2], tv->pext1);
Multicast(tv, nqversion.data, nqversion.cursize, to, mask, NQ);
}
static void ParseDamage(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
ReadByte (m);
ReadByte (m);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, QW);
}
enum {
TE_SPIKE = 0,
TE_SUPERSPIKE = 1,
TE_GUNSHOT = 2,
TE_EXPLOSION = 3,
TE_TAREXPLOSION = 4,
TE_LIGHTNING1 = 5,
TE_LIGHTNING2 = 6,
TE_WIZSPIKE = 7,
TE_KNIGHTSPIKE = 8,
TE_LIGHTNING3 = 9,
TE_LAVASPLASH = 10,
TE_TELEPORT = 11,
TE_BLOOD = 12,
TE_LIGHTNINGBLOOD = 13,
};
static void ParseTempEntity(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
int i;
int dest = QW;
char nqversion[64];
int nqversionlength=0;
i = ReadByte (m);
switch(i)
{
case TE_SPIKE:
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
dest |= NQ;
break;
case TE_SUPERSPIKE:
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
dest |= NQ;
break;
case TE_GUNSHOT:
ReadByte (m);
nqversion[0] = svc_temp_entity;
nqversion[1] = TE_GUNSHOT;
if (tv->pext1 & PEXT_FLOATCOORDS)
nqversionlength = 2+3*4;
else
nqversionlength = 2+3*2;
for (i = 2; i < nqversionlength; i++)
nqversion[i] = ReadByte (m);
break;
case TE_EXPLOSION:
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
dest |= NQ;
break;
case TE_TAREXPLOSION:
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
dest |= NQ;
break;
case TE_LIGHTNING1:
case TE_LIGHTNING2:
case TE_LIGHTNING3:
ReadShort (m);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
dest |= NQ;
break;
case TE_WIZSPIKE:
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
dest |= NQ;
break;
case TE_KNIGHTSPIKE:
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
dest |= NQ;
break;
case TE_LAVASPLASH:
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
dest |= NQ;
break;
case TE_TELEPORT:
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
dest |= NQ;
break;
case TE_BLOOD:
ReadByte (m);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
//FIXME: generate svc_particle for nq
break;
case TE_LIGHTNINGBLOOD:
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
ReadCoord (m, tv->pext1);
//FIXME: generate svc_particle for nq
break;
default:
Sys_Printf(tv->cluster, "temp entity %i not recognised\n", i);
return;
}
Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, to, mask, dest);
if (nqversionlength)
Multicast(tv, nqversion, nqversionlength, to, mask, NQ);
}
void ParseLightstyle(sv_t *tv, netmsg_t *m)
{
int style;
style = ReadByte(m);
if (style < MAX_LIGHTSTYLES)
ReadString(m, tv->map.lightstyle[style].name, sizeof(tv->map.lightstyle[style].name));
else
{
Sys_Printf(tv->cluster, "svc_lightstyle: invalid lightstyle index (%i)\n", style);
while (ReadByte(m)) //suck out the message.
{
}
}
Multicast(tv, (char*)m->data+m->startpos, m->readpos - m->startpos, dem_read, (unsigned)-1, Q1);
}
void ParseNails(sv_t *tv, netmsg_t *m, qboolean nails2)
{
int count;
int i;
count = (unsigned char)ReadByte(m);
while(count > sizeof(tv->map.nails) / sizeof(tv->map.nails[0]))
{//they sent too many, suck it out.
count--;
if (nails2)
ReadByte(m);
for (i = 0; i < 6; i++)
ReadByte(m);
}
tv->map.nailcount = count;
while(count-- > 0)
{
if (nails2)
tv->map.nails[count].number = ReadByte(m);
else
tv->map.nails[count].number = count;
for (i = 0; i < 6; i++)
tv->map.nails[count].bits[i] = ReadByte(m);
}
}
void ParseDownload(sv_t *tv, netmsg_t *m)
{
//warning this needs looking at (controller downloads)
int size, b;
unsigned int percent;
char buffer[2048];
size = (signed short)ReadShort(m);
percent = ReadByte(m);
if (size < 0)
{
Sys_Printf(tv->cluster, "Downloading failed\n");
if (tv->downloadfile)
fclose(tv->downloadfile);
tv->downloadfile = NULL;
tv->errored = ERR_PERMANENT;
QW_StreamPrint(tv->cluster, tv, NULL, "Map download failed\n");
return;
}
for (b = 0; b < size; b++)
buffer[b] = ReadByte(m);
if (!tv->downloadfile)
{
Sys_Printf(tv->cluster, "Not downloading anything\n");
tv->errored = ERR_PERMANENT;
return;
}
fwrite(buffer, 1, size, tv->downloadfile);
if (percent == 100)
{
fclose(tv->downloadfile);
tv->downloadfile = NULL;
snprintf(buffer, sizeof(buffer), "%s/%s", (*tv->map.gamedir)?tv->map.gamedir:"id1", tv->map.modellist[1].name);
rename(tv->downloadname, buffer);
Sys_Printf(tv->cluster, "Download complete\n");
tv->map.bsp = BSP_LoadModel(tv->cluster, tv->map.gamedir, tv->map.modellist[1].name);
if (!tv->map.bsp)
{
Sys_Printf(tv->cluster, "Failed to read BSP\n");
tv->errored = ERR_PERMANENT;
}
else
{
SendClientCommand(tv, "prespawn %i 0 %i\n", tv->clservercount, LittleLong(BSP_Checksum(tv->map.bsp)));
strcpy(tv->status, "Prespawning\n");
}
}
else
{
snprintf(tv->status, sizeof(tv->status), "Downloading map, %i%%\n", percent);
SendClientCommand(tv, "nextdl\n");
}
}
void ParseMessage(sv_t *tv, void *buffer, int length, int to, int mask)
{
int lastsvc;
int svc = -1;
int i;
netmsg_t buf;
qboolean clearoldplayers = true;
buf.cursize = length;
buf.maxsize = length;
buf.readpos = 0;
buf.data = buffer;
buf.startpos = 0;
while(buf.readpos < buf.cursize)
{
lastsvc = svc;
if (buf.readpos > buf.cursize)
{
Sys_Printf(tv->cluster, "Read past end of parse buffer\n, last was %i\n", lastsvc);
return;
}
buf.startpos = buf.readpos;
svc = ReadByte(&buf);
// printf("%i\n", svc);
switch (svc)
{
case svc_bad:
ParseError(&buf);
Sys_Printf(tv->cluster, "ParseMessage: svc_bad, last was %i\n", lastsvc);
return;
case svc_nop: //quakeworld isn't meant to send these.
QTV_Printf(tv, "nop\n");
break;
case svc_disconnect:
//mvdsv safely terminates it's mvds with an svc_disconnect.
//the client is meant to read that and disconnect without reading the intentionally corrupt packet following it.
//however, our demo playback is chained and looping and buffered.
//so we've already found the end of the source file and restarted parsing.
//in fte at least, the server does give the packet the correct length
//I hope mvdsv is the same
if (tv->sourcetype != SRC_DEMO)
{
#ifndef _MSC_VER
#warning QTV is meant to disconnect when servers tells it to.
#endif
// FIXME: Servers are today sending the svc_disconnect in a non-standard way, which makes QTV drop when it shouldn't.
// Tell the server developers to fix the servers.
//tv->drop = true;
}
else
{
while(ReadByte(&buf))
;
}
return;
case svc_updatestat:
ParseUpdateStat(tv, &buf, to, mask);
break;
//#define svc_version 4 // [long] server version
case svc_nqsetview:
ReadShort(&buf);
//no actual handling is done!
break;
case svc_sound:
ParseSound(tv, &buf, to, mask);
break;
case svc_nqtime:
ReadFloat(&buf);
//no actual handling is done!
break;
case svc_print:
ParsePrint(tv, &buf, to, mask);
break;
case svc_stufftext:
ParseStufftext(tv, &buf, to, mask);
break;
case svc_setangle:
if (!tv->usequakeworldprotocols)
ReadByte(&buf);
tv->proxyplayerangles[0] = ReadAngle(&buf, tv->pext1);
tv->proxyplayerangles[1] = ReadAngle(&buf, tv->pext1);
tv->proxyplayerangles[2] = ReadAngle(&buf, tv->pext1);
if (tv->usequakeworldprotocols && tv->controller)
SendBufferToViewer(tv->controller, (char*)buf.data+buf.startpos, buf.readpos - buf.startpos, true);
/*{
char nq[7];
nq[0] = svc_setangle;
nq[1] = tv->proxyplayerangles[0];
nq[2] = tv->proxyplayerangles[1];
nq[3] = tv->proxyplayerangles[2];
// Multicast(tv, nq, 4, to, mask, Q1);
}*/
break;
case svc_serverdata:
ParseServerData(tv, &buf, to, mask);
break;
case svc_lightstyle:
ParseLightstyle(tv, &buf);
break;
//#define svc_updatename 13 // [qbyte] [string]
case svc_updatefrags:
ParseUpdateFrags(tv, &buf, to, mask);
break;
//#define svc_clientdata 15 // <shortbits + data>
//#define svc_stopsound 16 // <see code>
//#define svc_updatecolors 17 // [qbyte] [qbyte] [qbyte]
case svc_particle:
ReadCoord(&buf, tv->pext1);
ReadCoord(&buf, tv->pext1);
ReadCoord(&buf, tv->pext1);
ReadByte(&buf);
ReadByte(&buf);
ReadByte(&buf);
ReadByte(&buf);
ReadByte(&buf);
Multicast(tv, (char*)buf.data+buf.startpos, buf.readpos - buf.startpos, dem_read, (unsigned)-1, Q1);
break;
case svc_damage:
ParseDamage(tv, &buf, to, mask);
break;
case svc_spawnstatic:
ParseSpawnStatic(tv, &buf, to, mask, false);
break;
case svcfte_spawnstatic2:
if (tv->pext1 & PEXT_SPAWNSTATIC2)
ParseSpawnStatic(tv, &buf, to, mask, true);
else
goto badsvc;
break;
case svc_spawnbaseline:
ParseBaseline(tv, &buf, to, mask, false);
break;
case svcfte_spawnbaseline2:
if (tv->pext1 & PEXT_SPAWNSTATIC2)
ParseBaseline(tv, &buf, to, mask, true);
else
goto badsvc;
break;
case svc_temp_entity:
ParseTempEntity(tv, &buf, to, mask);
break;
case svc_setpause: // [qbyte] on / off
tv->map.ispaused = ReadByte(&buf);
Multicast(tv, (char*)buf.data+buf.startpos, buf.readpos - buf.startpos, dem_read, (unsigned)-1, Q1);
break;
//#define svc_signonnum 25 // [qbyte] used for the signon sequence
case svc_centerprint:
ParseCenterprint(tv, &buf, to, mask);
break;
case svc_spawnstaticsound:
ParseStaticSound(tv, &buf, to, mask);
break;
case svc_intermission:
ParseIntermission(tv, &buf, to, mask);
break;
case svc_finale:
while(ReadByte(&buf))
;
Multicast(tv, (char*)buf.data+buf.startpos, buf.readpos - buf.startpos, dem_read, (unsigned)-1, Q1);
break;
case svc_cdtrack:
ParseCDTrack(tv, &buf, to, mask);
break;
case svc_sellscreen:
Multicast(tv, (char*)buf.data+buf.startpos, buf.readpos - buf.startpos, dem_read, (unsigned)-1, Q1);
break;
//#define svc_cutscene 34 //hmm... nq only... added after qw tree splitt?
case svc_smallkick:
case svc_bigkick:
Multicast(tv, (char*)buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask, QW);
break;
case svc_updateping:
ParseUpdatePing(tv, &buf, to, mask);
break;
case svc_updateentertime:
ParseUpdateEnterTime(tv, &buf, to, mask);
break;
case svc_updatestatlong:
ParseUpdateStatLong(tv, &buf, to, mask);
break;
case svc_muzzleflash:
ReadShort(&buf);
Multicast(tv, (char*)buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask, QW);
break;
case svc_updateuserinfo:
ParseUpdateUserinfo(tv, &buf, to, mask);
break;
case svc_download: // [short] size [size bytes]
ParseDownload(tv, &buf);
break;
case svc_playerinfo:
ParsePlayerInfo(tv, &buf, clearoldplayers);
clearoldplayers = false;
break;
case svc_nails:
ParseNails(tv, &buf, false);
break;
case svc_chokecount:
ReadByte(&buf);
break;
case svcfte_modellistshort:
case svc_modellist:
i = ParseList(tv, &buf, tv->map.modellist, to, mask, svc==svcfte_modellistshort);
if (!i)
{
int j;
if (tv->map.bsp)
BSP_Free(tv->map.bsp);
if (tv->cluster->nobsp)// || !tv->usequkeworldprotocols)
tv->map.bsp = NULL;
else
tv->map.bsp = BSP_LoadModel(tv->cluster, tv->map.gamedir, tv->map.modellist[1].name);
tv->map.numinlines = 0;
for (j = 2; j < 256; j++)
{
if (*tv->map.modellist[j].name != '*')
break;
tv->map.numinlines = j;
}
tv->map.modelindex_player = 0;
tv->map.modelindex_spike = 0;
for (j = 2; j < 256; j++)
{
if (!*tv->map.modellist[j].name)
break;
if (!strcmp(tv->map.modellist[j].name, "progs/player.mdl"))
tv->map.modelindex_player = j;
if (!strcmp(tv->map.modellist[j].name, "progs/spike.mdl"))
tv->map.modelindex_spike = j;
}
strcpy(tv->status, "Prespawning\n");
}
ConnectionData(tv, (void*)((char*)buf.data+buf.startpos), buf.readpos - buf.startpos, to, mask, QW);
if ((!tv->controller || tv->controller->netchan.isnqprotocol) && tv->usequakeworldprotocols)
{
if (i)
SendClientCommand(tv, "modellist %i %i\n", tv->clservercount, i);
else if (!tv->map.bsp && !tv->cluster->nobsp)
{
if (tv->downloadfile)
{
fclose(tv->downloadfile);
unlink(tv->downloadname);
Sys_Printf(tv->cluster, "Was already downloading %s\nOld download canceled\n", tv->downloadname);
tv->downloadfile = NULL;
}
snprintf(tv->downloadname, sizeof(tv->downloadname), "%s/%s.tmp", (*tv->map.gamedir)?tv->map.gamedir:"id1", tv->map.modellist[1].name);
QTV_mkdir(tv->downloadname);
tv->downloadfile = fopen(tv->downloadname, "wb");
if (!tv->downloadfile)
{
Sys_Printf(tv->cluster, "Couldn't open temporary file %s\n", tv->downloadname);
SendClientCommand(tv, "prespawn %i 0 %i\n", tv->clservercount, LittleLong(BSP_Checksum(tv->map.bsp)));
}
else
{
char buffer[512];
strcpy(tv->status, "Downloading map\n");
Sys_Printf(tv->cluster, "Attempting download of %s\n", tv->downloadname);
SendClientCommand(tv, "download %s\n", tv->map.modellist[1].name);
snprintf(buffer, sizeof(buffer), "[QTV] Attempting map download (%s)\n", tv->map.modellist[1].name);
QW_StreamPrint(tv->cluster, tv, NULL, buffer);
}
}
else
{
SendClientCommand(tv, "prespawn %i 0 %i\n", tv->clservercount, LittleLong(BSP_Checksum(tv->map.bsp)));
}
}
break;
case svcfte_soundlistshort:
case svc_soundlist:
i = ParseList(tv, &buf, tv->map.soundlist, to, mask, svc==svcfte_soundlistshort);
if (!i)
strcpy(tv->status, "Receiving modellist\n");
ConnectionData(tv, (void*)((char*)buf.data+buf.startpos), buf.readpos - buf.startpos, to, mask, QW);
if ((!tv->controller || tv->controller->netchan.isnqprotocol) && tv->usequakeworldprotocols)
{
if (i)
SendClientCommand(tv, "soundlist %i %i\n", tv->clservercount, i);
else
SendClientCommand(tv, "modellist %i 0\n", tv->clservercount);
}
break;
case svc_packetentities:
// FlushPacketEntities(tv);
ParsePacketEntities(tv, &buf, -1);
break;
case svc_deltapacketentities:
ParsePacketEntities(tv, &buf, ReadByte(&buf));
break;
case svc_entgravity: // gravity change, for prediction
ReadFloat(&buf);
Multicast(tv, (char*)buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask, QW);
break;
case svc_maxspeed: // maxspeed change, for prediction
ReadFloat(&buf);
Multicast(tv, (char*)buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask, QW);
break;
case svc_setinfo:
ParseSetInfo(tv, &buf);
break;
case svc_serverinfo:
ParseServerinfo(tv, &buf);
break;
case svc_updatepl:
ParsePacketloss(tv, &buf, to, mask);
break;
case svc_nails2:
ParseNails(tv, &buf, true);
break;
case svc_killedmonster:
Multicast(tv, (char*)buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask, Q1);
break;
case svc_foundsecret:
Multicast(tv, (char*)buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask, Q1);
break;
default:
badsvc:
buf.readpos = buf.startpos;
Sys_Printf(tv->cluster, "Can't handle svc %i, last was %i\n", (unsigned int)ReadByte(&buf), lastsvc);
return;
}
}
}