fa0c73d33b
Added .psd, .pbm/.pgm/.ppm, .pfm, and .hdr image formats. Extensions NOT added to r_imageextensions. png (and the above formats) can now be loaded as RGBA16, instead of being truncated to RGBA8 (8bit pngs not affected). r_imagelist will now show images from memory, instead of potentially loading new/different ones from disk. Fix serverbrowser bug being too eager to join the server (eg from alt+tab). Don't send ipv6 packets to qw/q2 masters. They won't be able to report ipv6 addresses anyway, and this reduces warnings when a host STILL has no ipv6 (my ISP sucks). this does not affect q3/dpmasters, for people without ipv4 addresses. Tried to improve compat with Bloodshot's particle effects. Fixed a couple of issues with R_AddTrisoup. Fixed string tokenizing bug where it was using the wrong buffer size values. Don't show link-local/localhost addresses in eg the status command (unless developer). qtv-rel is now an easier target, for new qtv releases. qtv warning fixes. added a nailtrail effect to 'high' particles. fixed terrain shaders. fixed fogged water issue (on one of bal's maps). first attempt at gltf2 format support git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5400 fc73d0e0-1445-4013-8a0c-d673dee63da5
2056 lines
56 KiB
C
2056 lines
56 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"
|
|
|
|
#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));
|
|
|
|
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, 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);
|
|
}
|
|
|
|
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,
|
|
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,
|
|
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);
|
|
|
|
|
|
if (channel & SND_VOLUME)
|
|
vol = ReadByte (m);
|
|
else
|
|
vol = DEFAULT_SOUND_PACKET_VOLUME;
|
|
|
|
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)?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)?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;
|
|
}
|
|
}
|
|
}
|
|
|