b9cd6ec91b
_pext_vrinputs: added cvar to enable vr inputs protocol extension allowing vr inputs to be networked to ssqc too. defaults to 0 for now, will be renamed when deemed final. updates menu: the prompt to enable sources is now more explicit instead of expecting the user to have a clue. updates menu: added a v3 sources format, which should be more maintainable. not final. updates menu: try to give reasons why sources might be failing (to help blame ISPs if they try fucking over TTH dns again). presets menu: no longer closes the instant a preset is chosen. some presets have a couple of modifiers listed. force the demo loop in the background to serve as a preview. prompts menus: now does word wrapping. ftemaster: support importing server lists from other master servers (requested by Eukara). server: try to detect when non-reply inbound packets are blocked by firewalls/nats/etc (using ftemaster to do so). qcvm: added pointcontentsmask builtin, allowing it to probe more than just world, with fte's full contentbit range instead of just q1 legacy. qcvm: memfill8 builtin now works on createbuffer() pointers. qcvm: add missing unsigned ops. Fixed double comparison ops. fixed bug with op_store_i64. added missing OP_LOADP_I64 qcc: added '#pragma framerate RATE' for overriding implicit nextthink durations. qcc: fixed '#pragma DONT_COMPILE_THIS_FILE' to not screw up comments. qcc: added __GITURL__ __GITHASH__ __GITDATE__ __GITDATETIME__ __GITDESC__ for any mods that might want to make use of that. qcc: fix up -Fhashonly a little setrenderer: support for vulkan gpu enumeration. rulesets: reworked to support custom rulesets (using hashes to catch haxxors, though still nothing prevents just changing the client to ignore rulesets) bspx: use our BIH code for the bspx BRUSHLIST lump instead of the older less efficient code. (static)iqm+obj: these model formats can now be used for the worldmodel (with a suitable .ent file). Also using BIH for much better collision performance. pmove: tried to optimise PM_NudgePosition, should boost fps in stress tests. wayland: fix a crash on startup. mousegrabs now works better. imagetool: uses sdl for previews. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5813 fc73d0e0-1445-4013-8a0c-d673dee63da5
1085 lines
25 KiB
C
1085 lines
25 KiB
C
#include "quakedef.h"
|
|
#include "shader.h"
|
|
|
|
|
|
//urm, yeah, this is more than just parse.
|
|
|
|
#ifdef Q3CLIENT
|
|
|
|
#include "clq3defs.h"
|
|
|
|
#define SHOWSTRING(s) if(cl_shownet.value==2)Con_Printf ("%s\n", s);
|
|
#define SHOWNET(x) if(cl_shownet.value==2)Con_Printf ("%3i:%s\n", msg_readcount-1, x);
|
|
#define SHOWNET2(x, y) if(cl_shownet.value==2)Con_Printf ("%3i:%3i:%s\n", msg_readcount-1, y, x);
|
|
|
|
void MSG_WriteBits(sizebuf_t *msg, int value, int bits);
|
|
|
|
|
|
ClientConnectionState_t ccs;
|
|
|
|
|
|
|
|
qboolean CG_FillQ3Snapshot(int snapnum, snapshot_t *snapshot)
|
|
{
|
|
int i;
|
|
clientSnap_t *snap;
|
|
|
|
if (snapnum > ccs.serverMessageNum)
|
|
{
|
|
Host_EndGame("CG_FillQ3Snapshot: snapshotNumber > cl.snap.serverMessageNum");
|
|
}
|
|
|
|
if (ccs.serverMessageNum - snapnum >= Q3UPDATE_BACKUP)
|
|
{
|
|
return false; // too old
|
|
}
|
|
|
|
snap = &ccs.snapshots[snapnum & Q3UPDATE_MASK];
|
|
if(!snap->valid || snap->serverMessageNum != snapnum)
|
|
{
|
|
return false; // invalid
|
|
}
|
|
|
|
memcpy(&snapshot->ps, &snap->playerstate, sizeof(snapshot->ps));
|
|
snapshot->numEntities = snap->numEntities;
|
|
for (i=0; i<snapshot->numEntities; i++)
|
|
{
|
|
memcpy(&snapshot->entities[i], &ccs.parseEntities[(snap->firstEntity+i) & Q3PARSE_ENTITIES_MASK], sizeof(snapshot->entities[0]));
|
|
}
|
|
|
|
memcpy( &snapshot->areamask, snap->areabits, sizeof( snapshot->areamask ) );
|
|
|
|
snapshot->snapFlags = snap->snapFlags;
|
|
snapshot->ping = snap->ping;
|
|
|
|
snapshot->serverTime = snap->serverTime;
|
|
|
|
snapshot->numServerCommands = snap->serverCommandNum;
|
|
snapshot->serverCommandSequence = ccs.lastServerCommandNum;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CLQ3_ParseServerCommand
|
|
=====================
|
|
*/
|
|
void CLQ3_ParseServerCommand(void)
|
|
{
|
|
int number;
|
|
char *string;
|
|
|
|
number = MSG_ReadLong();
|
|
SHOWNET(va("%i", number));
|
|
|
|
string = MSG_ReadString();
|
|
SHOWSTRING(string);
|
|
|
|
if( number <= ccs.lastServerCommandNum )
|
|
{
|
|
return; // we have already received this command
|
|
}
|
|
|
|
ccs.lastServerCommandNum++;
|
|
|
|
if( number > ccs.lastServerCommandNum+Q3TEXTCMD_MASK-1 )
|
|
{
|
|
Con_Printf("Warning: Lost %i reliable serverCommands\n",
|
|
number - ccs.lastServerCommandNum );
|
|
}
|
|
|
|
// archive the command to be processed by cgame later
|
|
Q_strncpyz( ccs.serverCommands[number & Q3TEXTCMD_MASK], string, sizeof( ccs.serverCommands[0] ) );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_DeltaEntity
|
|
|
|
Parses deltas from the given base and adds the resulting entity
|
|
to the current frame
|
|
==================
|
|
*/
|
|
static void CLQ3_DeltaEntity( clientSnap_t *frame, int newnum, q3entityState_t *old, qboolean unchanged )
|
|
{
|
|
q3entityState_t *state;
|
|
|
|
state = &ccs.parseEntities[ccs.firstParseEntity & Q3PARSE_ENTITIES_MASK];
|
|
|
|
if( unchanged )
|
|
{
|
|
memcpy( state, old, sizeof(*state) ); // don't read any bits
|
|
}
|
|
else
|
|
{
|
|
if (!MSG_Q3_ReadDeltaEntity(old, state, newnum)) // the entity present in oldframe is not in the current frame
|
|
return;
|
|
}
|
|
|
|
ccs.firstParseEntity++;
|
|
frame->numEntities++;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParsePacketEntities
|
|
|
|
An svc_packetentities has just been parsed, deal with the
|
|
rest of the data stream.
|
|
==================
|
|
*/
|
|
static void CLQ3_ParsePacketEntities( clientSnap_t *oldframe, clientSnap_t *newframe )
|
|
{
|
|
int numentities;
|
|
int oldnum;
|
|
int newnum;
|
|
q3entityState_t *oldstate;
|
|
|
|
oldstate = NULL;
|
|
newframe->firstEntity = ccs.firstParseEntity;
|
|
newframe->numEntities = 0;
|
|
|
|
// delta from the entities present in oldframe
|
|
numentities = 0;
|
|
if( !oldframe )
|
|
{
|
|
oldnum = 99999;
|
|
}
|
|
else if( oldframe->numEntities <= 0 )
|
|
{
|
|
oldnum = 99999;
|
|
}
|
|
else
|
|
{
|
|
oldstate = &ccs.parseEntities[oldframe->firstEntity & Q3PARSE_ENTITIES_MASK];
|
|
oldnum = oldstate->number;
|
|
}
|
|
|
|
while( 1 )
|
|
{
|
|
newnum = MSG_ReadBits( GENTITYNUM_BITS );
|
|
if( newnum < 0 || newnum >= MAX_GENTITIES )
|
|
{
|
|
Host_EndGame("CLQ3_ParsePacketEntities: bad number %i", newnum);
|
|
}
|
|
|
|
if( msg_readcount > net_message.cursize )
|
|
{
|
|
Host_EndGame("CLQ3_ParsePacketEntities: end of message");
|
|
}
|
|
|
|
// end of packetentities
|
|
if( newnum == ENTITYNUM_NONE )
|
|
{
|
|
break;
|
|
}
|
|
|
|
while( oldnum < newnum )
|
|
{
|
|
// one or more entities from the old packet are unchanged
|
|
SHOWSTRING( va( "unchanged: %i", oldnum ) );
|
|
|
|
CLQ3_DeltaEntity( newframe, oldnum, oldstate, true );
|
|
|
|
numentities++;
|
|
|
|
if( numentities >= oldframe->numEntities )
|
|
{
|
|
oldnum = 99999;
|
|
}
|
|
else
|
|
{
|
|
oldstate = &ccs.parseEntities[(oldframe->firstEntity + numentities) & Q3PARSE_ENTITIES_MASK];
|
|
oldnum = oldstate->number;
|
|
}
|
|
}
|
|
|
|
if( oldnum == newnum )
|
|
{
|
|
// delta from previous state
|
|
SHOWSTRING( va( "delta: %i", newnum ) );
|
|
|
|
CLQ3_DeltaEntity( newframe, newnum, oldstate, false );
|
|
|
|
numentities++;
|
|
|
|
if( numentities >= oldframe->numEntities )
|
|
{
|
|
oldnum = 99999;
|
|
}
|
|
else
|
|
{
|
|
oldstate = &ccs.parseEntities[(oldframe->firstEntity + numentities) & Q3PARSE_ENTITIES_MASK];
|
|
oldnum = oldstate->number;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if( oldnum > newnum )
|
|
{
|
|
// delta from baseline
|
|
SHOWSTRING( va( "baseline: %i", newnum ) );
|
|
|
|
CLQ3_DeltaEntity( newframe, newnum, &ccs.baselines[newnum], false );
|
|
}
|
|
}
|
|
|
|
// any remaining entities in the old frame are copied over
|
|
while( oldnum != 99999 )
|
|
{
|
|
// one or more entities from the old packet are unchanged
|
|
SHOWSTRING( va( "unchanged: %i", oldnum ) );
|
|
|
|
CLQ3_DeltaEntity( newframe, oldnum, oldstate, true );
|
|
|
|
numentities++;
|
|
|
|
if( numentities >= oldframe->numEntities )
|
|
{
|
|
oldnum = 99999;
|
|
}
|
|
else
|
|
{
|
|
oldstate = &ccs.parseEntities[(oldframe->firstEntity + numentities) & Q3PARSE_ENTITIES_MASK];
|
|
oldnum = oldstate->number;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CLQ3_ParseSnapshot(void)
|
|
{
|
|
clientSnap_t snap, *oldsnap;
|
|
int delta;
|
|
int len;
|
|
int i;
|
|
outframe_t *frame;
|
|
// usercmd_t *ucmd;
|
|
// int commandTime;
|
|
|
|
memset(&snap, 0, sizeof(snap));
|
|
snap.serverMessageNum = ccs.serverMessageNum;
|
|
snap.serverCommandNum = ccs.lastServerCommandNum;
|
|
snap.serverTime = MSG_ReadLong();
|
|
|
|
//so we can delta to it properly.
|
|
cl.oldgametime = cl.gametime;
|
|
cl.oldgametimemark = cl.gametimemark;
|
|
cl.gametime = snap.serverTime / 1000.0f;
|
|
cl.gametimemark = Sys_DoubleTime();
|
|
|
|
// If the frame is delta compressed from data that we
|
|
// no longer have available, we must suck up the rest of
|
|
// the frame, but not use it, then ask for a non-compressed message
|
|
delta = MSG_ReadByte();
|
|
if(delta)
|
|
{
|
|
snap.deltaFrame = ccs.serverMessageNum - delta;
|
|
oldsnap = &ccs.snapshots[snap.deltaFrame & Q3UPDATE_MASK];
|
|
|
|
if(!oldsnap->valid)
|
|
{
|
|
// should never happen
|
|
Con_Printf( "Delta from invalid frame (not supposed to happen!).\n");
|
|
}
|
|
else if( oldsnap->serverMessageNum != snap.deltaFrame )
|
|
{
|
|
// The frame that the server did the delta from
|
|
// is too old, so we can't reconstruct it properly.
|
|
Con_DPrintf( "Delta frame too old.\n" );
|
|
}
|
|
else if(ccs.firstParseEntity - oldsnap->firstEntity >
|
|
Q3MAX_PARSE_ENTITIES - MAX_ENTITIES_IN_SNAPSHOT)
|
|
{
|
|
Con_DPrintf( "Delta parse_entities too old.\n" );
|
|
}
|
|
else
|
|
{
|
|
snap.valid = true; // valid delta parse
|
|
}
|
|
}
|
|
else
|
|
{
|
|
oldsnap = NULL;
|
|
snap.deltaFrame = -1;
|
|
snap.valid = true; // uncompressed frame
|
|
}
|
|
|
|
// read snapFlags
|
|
snap.snapFlags = MSG_ReadByte();
|
|
|
|
// read areabits
|
|
len = MSG_ReadByte();
|
|
MSG_ReadData(snap.areabits, len );
|
|
|
|
// read playerinfo
|
|
SHOWSTRING("playerstate");
|
|
MSG_Q3_ReadDeltaPlayerstate(oldsnap ? &oldsnap->playerstate : NULL, &snap.playerstate);
|
|
|
|
// read packet entities
|
|
SHOWSTRING("packet entities");
|
|
CLQ3_ParsePacketEntities(oldsnap, &snap);
|
|
|
|
if (!snap.valid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// cl.adjustTimeDelta = true;
|
|
|
|
// Find last usercmd server has processed and calculate snap.ping
|
|
|
|
snap.ping = 3;
|
|
for (i=cls.netchan.outgoing_sequence-1 ; i>cls.netchan.outgoing_sequence-Q3CMD_BACKUP ; i--)
|
|
{
|
|
frame = &cl.outframes[i & Q3CMD_MASK];
|
|
if (frame->server_message_num == snap.deltaFrame)
|
|
{
|
|
snap.ping = Sys_Milliseconds() - frame->client_time;
|
|
break;
|
|
}
|
|
}
|
|
|
|
memcpy(&ccs.snap, &snap, sizeof(snap));
|
|
memcpy(&ccs.snapshots[ccs.serverMessageNum & Q3UPDATE_MASK], &snap, sizeof(snap));
|
|
|
|
SHOWSTRING(va("snapshot:%i delta:%i ping:%i", snap.serverMessageNum, snap.deltaFrame, snap.ping));
|
|
}
|
|
|
|
#define MAXCHUNKSIZE 65536
|
|
void CLQ3_ParseDownload(void)
|
|
{
|
|
qdownload_t *dl = cls.download;
|
|
unsigned int chunknum;
|
|
unsigned int chunksize;
|
|
unsigned char chunkdata[MAXCHUNKSIZE];
|
|
int i;
|
|
char *s;
|
|
|
|
chunknum = (unsigned short) MSG_ReadShort();
|
|
chunknum |= ccs.downloadchunknum&~0xffff; //add the chunk number, truncated by the network protocol.
|
|
|
|
if (!chunknum)
|
|
{
|
|
dl->size = (unsigned int)MSG_ReadLong();
|
|
Cvar_SetValue( Cvar_Get("cl_downloadSize", "0", 0, "Download stuff"), dl->size );
|
|
}
|
|
|
|
if (dl->size == (unsigned int)-1)
|
|
{ //the only downloads we should be getting is pk3s.
|
|
//if they're advertised-but-failing then its probably due to permissions rather than file-not-found
|
|
s = MSG_ReadString();
|
|
CL_DownloadFailed(dl->remotename, dl, DLFAIL_SERVERCVAR);
|
|
Host_EndGame("%s", s);
|
|
return;
|
|
}
|
|
|
|
chunksize = (unsigned short)MSG_ReadShort();
|
|
if (chunksize > MAXCHUNKSIZE)
|
|
Host_EndGame("Server sent a download chunk of size %i (it's too damn big!)\n", chunksize);
|
|
|
|
for (i = 0; i < chunksize; i++)
|
|
chunkdata[i] = MSG_ReadByte();
|
|
|
|
if (ccs.downloadchunknum != chunknum) //the q3 client is rather lame.
|
|
{ //ccs.downloadchunknum holds the chunk number.
|
|
Con_DPrintf("PACKETLOSS WHEN DOWNLOADING!!!!\n");
|
|
return; //let the server try again some time
|
|
}
|
|
ccs.downloadchunknum++;
|
|
|
|
if (!dl || dl->method != DL_Q3)
|
|
{
|
|
Con_Printf("Server sending download, but no download was requested\n");
|
|
CLQ3_SendClientCommand("stopdl");
|
|
return;
|
|
}
|
|
|
|
if (!dl->file)
|
|
{
|
|
if (!DL_Begun(dl))
|
|
{
|
|
CL_DownloadFailed(dl->remotename, dl, DLFAIL_CLIENTFILE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Con_DPrintf("dl: chnk %u, size %u, csize %u\n", (unsigned int)chunknum, (unsigned int)dl->size, (unsigned int)chunksize);
|
|
|
|
if (!chunksize)
|
|
{
|
|
CL_DownloadFinished(dl);
|
|
|
|
FS_ReloadPackFiles();
|
|
|
|
cl.servercount = -1; //make sure the server resends us that vital gamestate.
|
|
ccs.downloadchunknum = -1;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
VFS_WRITE(dl->file, chunkdata, chunksize);
|
|
dl->ratebytes += chunksize;
|
|
chunksize=VFS_TELL(dl->file);
|
|
// Con_Printf("Recieved %i\n", chunksize);
|
|
|
|
dl->percent = (100.0 * chunksize) / dl->size;
|
|
}
|
|
|
|
|
|
CLQ3_SendClientCommand("nextdl %u", chunknum);
|
|
}
|
|
|
|
static qboolean CLQ3_SendDownloads(char *rc, char *rn)
|
|
{
|
|
while(rn)
|
|
{
|
|
qdownload_t *dl;
|
|
char localname[MAX_QPATH];
|
|
char tempname[MAX_QPATH];
|
|
char crc[64];
|
|
vfsfile_t *f;
|
|
extern cvar_t cl_downloads;
|
|
rc = COM_ParseOut(rc, crc, sizeof(crc));
|
|
rn = COM_Parse(rn);
|
|
if (!*com_token)
|
|
break;
|
|
|
|
if (!strchr(com_token, '/')) //don't let some muppet tell us to download quake3.exe
|
|
break;
|
|
|
|
//as much as I'd like to use COM_FCheckExists, this stuf is relative to root, not the gamedir.
|
|
f = FS_OpenVFS(va("%s.pk3", com_token), "rb", FS_ROOT);
|
|
if (f)
|
|
{
|
|
VFS_CLOSE(f);
|
|
continue;
|
|
}
|
|
if (!FS_GenCachedPakName(va("%s.pk3", com_token), crc, localname, sizeof(localname)))
|
|
continue;
|
|
f = FS_OpenVFS(localname, "rb", FS_ROOT);
|
|
if (f)
|
|
{
|
|
VFS_CLOSE(f);
|
|
continue;
|
|
}
|
|
|
|
if (!FS_GenCachedPakName(va("%s.tmp", com_token), crc, tempname, sizeof(tempname)))
|
|
continue;
|
|
|
|
if (!cl_downloads.ival)
|
|
{
|
|
Con_Printf(CON_WARNING "Need to download %s.pk3, but downloads are disabled\n", com_token);
|
|
continue;
|
|
}
|
|
|
|
//fixme: request to download it
|
|
Con_Printf("Sending request to download %s.pk3\n", com_token);
|
|
CLQ3_SendClientCommand("download %s.pk3", com_token);
|
|
ccs.downloadchunknum = 0;
|
|
dl = Z_Malloc(sizeof(*dl));
|
|
//q3's downloads are relative to root, but they do at least force a pk3 extension.
|
|
Q_snprintfz(dl->localname, sizeof(dl->localname), "package/%s", localname);
|
|
Q_snprintfz(dl->tempname, sizeof(dl->tempname), "package/%s", tempname);
|
|
dl->prefixbytes = 8;
|
|
dl->fsroot = FS_ROOT;
|
|
|
|
Q_snprintfz(dl->remotename, sizeof(dl->remotename), "%s.pk3", com_token);
|
|
dl->method = DL_Q3;
|
|
dl->percent = 0;
|
|
cls.download = dl;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
qboolean CLQ3_SystemInfoChanged(char *str)
|
|
{
|
|
qboolean usingpure, usingcheats;
|
|
char *value;
|
|
char *pc, *pn;
|
|
char *rc, *rn;
|
|
|
|
Con_Printf("Server's sv_pure: \"%s\"\n", Info_ValueForKey(str, "sv_pure"));
|
|
usingpure = atoi(Info_ValueForKey(str, "sv_pure"));
|
|
usingcheats = atoi(Info_ValueForKey(str, "sv_cheats"));
|
|
Cvar_ForceCheatVars(usingpure||usingcheats, usingcheats);
|
|
|
|
// if (atoi(value))
|
|
// Host_EndGame("Unable to connect to Q3 Pure Servers\n");
|
|
value = Info_ValueForKey(str, "fs_game");
|
|
|
|
#ifndef CLIENTONLY
|
|
if (!sv.state)
|
|
#endif
|
|
{
|
|
COM_FlushTempoaryPacks();
|
|
if (!*value)
|
|
value = "baseq3";
|
|
COM_Gamedir(value, NULL);
|
|
#ifndef CLIENTONLY
|
|
InfoBuf_SetStarKey (&svs.info, "*gamedir", value);
|
|
#endif
|
|
}
|
|
|
|
rc = Info_ValueForKey(str, "sv_referencedPaks"); //the ones that we should download.
|
|
rn = Info_ValueForKey(str, "sv_referencedPakNames");
|
|
if (CLQ3_SendDownloads(rc, rn))
|
|
return false;
|
|
|
|
pc = Info_ValueForKey(str, "sv_paks"); //the ones that we are allowed to use (in order!)
|
|
pn = Info_ValueForKey(str, "sv_pakNames");
|
|
FS_PureMode(usingpure?2:0, pn, pc, rn, rc, ccs.fs_key);
|
|
|
|
return true; //yay, we're in
|
|
}
|
|
|
|
void CLQ3_ParseGameState(void)
|
|
{
|
|
int c;
|
|
int index;
|
|
char *configString;
|
|
cvar_t *cl_paused;
|
|
|
|
//
|
|
// wipe the client_state_t struct
|
|
//
|
|
CL_ClearState(true);
|
|
ccs.firstParseEntity = 0;
|
|
memset(ccs.parseEntities, 0, sizeof(ccs.parseEntities));
|
|
memset(ccs.baselines, 0, sizeof(ccs.baselines));
|
|
|
|
|
|
cl.minpitch = -90;
|
|
cl.maxpitch = 90;
|
|
|
|
ccs.lastServerCommandNum = MSG_ReadLong();
|
|
|
|
for(;;)
|
|
{
|
|
c = MSG_ReadByte();
|
|
|
|
if(msg_badread)
|
|
{
|
|
Host_EndGame("CLQ3_ParseGameState: read past end of server message");
|
|
}
|
|
|
|
if(c == svcq3_eom)
|
|
{
|
|
break;
|
|
}
|
|
|
|
SHOWNET(va("%i", c));
|
|
|
|
switch(c)
|
|
{
|
|
default:
|
|
Host_EndGame("CLQ3_ParseGameState: bad command byte %i", c);
|
|
break;
|
|
|
|
case svcq3_configstring:
|
|
index = MSG_ReadBits(16);
|
|
if (index < 0 || index >= MAX_Q3_CONFIGSTRINGS)
|
|
{
|
|
Host_EndGame("CLQ3_ParseGameState: configString index %i out of range", index);
|
|
}
|
|
configString = MSG_ReadString();
|
|
|
|
CG_InsertIntoGameState(index, configString);
|
|
break;
|
|
|
|
case svcq3_baseline:
|
|
index = MSG_ReadBits(GENTITYNUM_BITS);
|
|
if (index < 0 || index >= MAX_GENTITIES)
|
|
{
|
|
Host_EndGame("CLQ3_ParseGameState: baseline index %i out of range", index);
|
|
}
|
|
MSG_Q3_ReadDeltaEntity(NULL, &ccs.baselines[index], index);
|
|
break;
|
|
}
|
|
}
|
|
|
|
cl.playerview[0].playernum = MSG_ReadLong();
|
|
ccs.fs_key = MSG_ReadLong();
|
|
|
|
if (!CLQ3_SystemInfoChanged(CG_GetConfigString(CFGSTR_SYSINFO)))
|
|
{
|
|
UI_Restart_f();
|
|
return;
|
|
}
|
|
|
|
CG_Restart_f();
|
|
UI_Restart_f();
|
|
|
|
if (!cl.worldmodel)
|
|
Host_EndGame("CGame didn't set a map.\n");
|
|
|
|
cl.model_precaches_added = false;
|
|
Surf_NewMap ();
|
|
|
|
SCR_EndLoadingPlaque();
|
|
|
|
CL_MakeActive("Quake3Arena");
|
|
|
|
cl.splitclients = 1;
|
|
|
|
{
|
|
char buffer[2048];
|
|
strcpy(buffer, va("cp %i ", cl.servercount));
|
|
FSQ3_GenerateClientPacksList(buffer, sizeof(buffer), ccs.fs_key);
|
|
CLQ3_SendClientCommand("%s", buffer); // warning: format not a string literal and no format arguments
|
|
}
|
|
|
|
// load cgame, etc
|
|
// CL_ChangeLevel();
|
|
|
|
cl_paused = Cvar_FindVar("cl_paused");
|
|
if (cl_paused && cl_paused->ival)
|
|
Cvar_ForceSet(cl_paused, "0");
|
|
|
|
}
|
|
|
|
void CLQ3_ParseServerMessage (void)
|
|
{
|
|
int cmd;
|
|
if (!CLQ3_Netchan_Process())
|
|
return; //was a fragment.
|
|
|
|
if (cl_shownet.value == 1)
|
|
Con_Printf ("%i ",net_message.cursize);
|
|
else if (cl_shownet.value == 2)
|
|
Con_Printf ("------------------\n");
|
|
|
|
net_message.packing = SZ_RAWBYTES;
|
|
MSG_BeginReading(msg_nullnetprim);
|
|
ccs.serverMessageNum = MSG_ReadLong();
|
|
net_message.packing = SZ_HUFFMAN; //the rest is huffman compressed.
|
|
net_message.currentbit = msg_readcount*8;
|
|
|
|
// read last client command number server received
|
|
ccs.lastClientCommandNum = MSG_ReadLong();
|
|
if( ccs.lastClientCommandNum <= ccs.numClientCommands - Q3TEXTCMD_BACKUP )
|
|
{
|
|
ccs.lastClientCommandNum = ccs.numClientCommands - Q3TEXTCMD_BACKUP + 1;
|
|
}
|
|
else if( ccs.lastClientCommandNum > ccs.numClientCommands )
|
|
{
|
|
ccs.lastClientCommandNum = ccs.numClientCommands;
|
|
}
|
|
|
|
//
|
|
// parse the message
|
|
//
|
|
for(;;)
|
|
{
|
|
cmd = MSG_ReadByte();
|
|
|
|
if(msg_badread) //hm, we have an eom, so only stop when the message is bad.
|
|
{
|
|
Host_EndGame("CLQ3_ParseServerMessage: read past end of server message");
|
|
break;
|
|
}
|
|
|
|
if(cmd == svcq3_eom)
|
|
{
|
|
SHOWNET2("END OF MESSAGE", 2);
|
|
break;
|
|
}
|
|
|
|
SHOWNET(va("%i", cmd));
|
|
|
|
// other commands
|
|
switch(cmd)
|
|
{
|
|
default:
|
|
Host_EndGame("CLQ3_ParseServerMessage: Illegible server message");
|
|
break;
|
|
case svcq3_nop:
|
|
break;
|
|
case svcq3_gamestate:
|
|
CLQ3_ParseGameState();
|
|
break;
|
|
case svcq3_serverCommand:
|
|
CLQ3_ParseServerCommand();
|
|
break;
|
|
case svcq3_download:
|
|
CLQ3_ParseDownload();
|
|
break;
|
|
case svcq3_snapshot:
|
|
CLQ3_ParseSnapshot();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
qboolean CLQ3_Netchan_Process(void)
|
|
{
|
|
#ifndef Q3_NOENCRYPT
|
|
int sequence;
|
|
int lastClientCommandNum;
|
|
qbyte bitmask;
|
|
qbyte c;
|
|
int i, j;
|
|
char *string;
|
|
int bit;
|
|
int readcount;
|
|
#endif
|
|
|
|
if(!Netchan_ProcessQ3(&cls.netchan))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#ifndef Q3_NOENCRYPT
|
|
// archive buffer state
|
|
bit = net_message.currentbit;
|
|
readcount = msg_readcount;
|
|
net_message.packing = SZ_HUFFMAN;
|
|
net_message.currentbit = 32;
|
|
|
|
lastClientCommandNum = MSG_ReadLong();
|
|
sequence = LittleLong(*(int *)net_message.data);
|
|
|
|
// restore buffer state
|
|
net_message.currentbit = bit;
|
|
msg_readcount = readcount;
|
|
|
|
// calculate bitmask
|
|
bitmask = (sequence ^ cls.challenge) & 0xff;
|
|
string = ccs.clientCommands[lastClientCommandNum & Q3TEXTCMD_MASK];
|
|
|
|
// decrypt the packet
|
|
for(i=msg_readcount+4,j=0 ; i<net_message.cursize ; i++,j++)
|
|
{
|
|
if(!string[j])
|
|
{
|
|
j = 0; // another way around
|
|
}
|
|
c = string[j];
|
|
if(c > 127 || c == '%')
|
|
{
|
|
c = '.';
|
|
}
|
|
bitmask ^= c << ((i-msg_readcount) & 1);
|
|
net_message.data[i] ^= bitmask;
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void CL_Netchan_Transmit( int length, const qbyte *data )
|
|
{
|
|
#define msg net_message
|
|
#ifndef Q3_NOENCRYPT
|
|
int serverid;
|
|
int lastSequence;
|
|
int lastServerCommandNum;
|
|
qbyte bitmask;
|
|
qbyte c;
|
|
int i, j;
|
|
char *string;
|
|
#endif
|
|
net_message.cursize = 0;
|
|
SZ_Write(&msg, data, length);
|
|
|
|
if(msg.overflowed)
|
|
{
|
|
Host_EndGame("Client message overflowed");
|
|
}
|
|
|
|
#ifndef Q3_NOENCRYPT
|
|
msg_readcount = 0;
|
|
msg.currentbit = 0;
|
|
msg.packing = SZ_HUFFMAN;
|
|
|
|
serverid = MSG_ReadLong();
|
|
lastSequence = MSG_ReadLong();
|
|
lastServerCommandNum = MSG_ReadLong();
|
|
|
|
// calculate bitmask
|
|
bitmask = (lastSequence ^ serverid ^ cls.challenge) & 0xff;
|
|
string = ccs.serverCommands[lastServerCommandNum & Q3TEXTCMD_MASK];
|
|
|
|
// encrypt the packet
|
|
for( i=12,j=0 ; i<msg.cursize ; i++,j++ )
|
|
{
|
|
if( !string[j] )
|
|
{
|
|
j = 0; // another way around
|
|
}
|
|
c = string[j];
|
|
if( c > 127 || c == '%' )
|
|
{
|
|
c = '.';
|
|
}
|
|
bitmask ^= c << (i & 1);
|
|
msg.data[i] ^= bitmask;
|
|
}
|
|
#endif
|
|
|
|
Netchan_TransmitQ3( &cls.netchan, msg.cursize, msg.data );
|
|
#undef msg
|
|
}
|
|
|
|
|
|
|
|
|
|
static void MSG_WriteDeltaKey( sizebuf_t *msg, int key, int from, int to, int bits )
|
|
{
|
|
if( from == to )
|
|
{
|
|
MSG_WriteBits( msg, 0, 1 );
|
|
return; // unchanged
|
|
}
|
|
|
|
MSG_WriteBits( msg, 1, 1 );
|
|
MSG_WriteBits( msg, to ^ key, bits );
|
|
}
|
|
|
|
void MSG_Q3_WriteDeltaUsercmd( sizebuf_t *msg, int key, const usercmd_t *from, const usercmd_t *to )
|
|
{
|
|
// figure out how to pack serverTime
|
|
if( to->servertime - from->servertime < 255 )
|
|
{
|
|
MSG_WriteBits(msg, 1, 1);
|
|
MSG_WriteBits(msg, to->servertime - from->servertime, 8);
|
|
}
|
|
else
|
|
{
|
|
MSG_WriteBits( msg, 0, 1 );
|
|
MSG_WriteBits( msg, to->servertime, 32);
|
|
}
|
|
|
|
if( !memcmp( (qbyte *)from + 4, (qbyte *)to + 4, sizeof( usercmd_t ) - 4 ) )
|
|
{
|
|
MSG_WriteBits(msg, 0, 1);
|
|
return; // nothing changed
|
|
}
|
|
MSG_WriteBits(msg, 1, 1);
|
|
|
|
key ^= to->servertime;
|
|
|
|
MSG_WriteDeltaKey(msg, key, from->angles[0], to->angles[0], 16);
|
|
MSG_WriteDeltaKey(msg, key, from->angles[1], to->angles[1], 16);
|
|
MSG_WriteDeltaKey(msg, key, from->angles[2], to->angles[2], 16);
|
|
MSG_WriteDeltaKey(msg, key, from->forwardmove, to->forwardmove, 8);
|
|
MSG_WriteDeltaKey(msg, key, from->sidemove, to->sidemove, 8 );
|
|
MSG_WriteDeltaKey(msg, key, from->upmove, to->upmove, 8);
|
|
MSG_WriteDeltaKey(msg, key, from->buttons, to->buttons, 16);
|
|
MSG_WriteDeltaKey(msg, key, from->weapon, to->weapon, 8);
|
|
}
|
|
|
|
|
|
|
|
void VARGS CLQ3_SendClientCommand(const char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
char command[MAX_STRING_CHARS];
|
|
|
|
va_start( argptr, fmt );
|
|
vsnprintf( command, sizeof(command), fmt, argptr );
|
|
va_end( argptr );
|
|
|
|
// create new clientCommand
|
|
ccs.numClientCommands++;
|
|
|
|
// check if server will lose some of our clientCommands
|
|
if(ccs.numClientCommands - ccs.lastClientCommandNum >= Q3TEXTCMD_BACKUP)
|
|
Host_EndGame("Client command overflow");
|
|
|
|
Q_strncpyz(ccs.clientCommands[ccs.numClientCommands & Q3TEXTCMD_MASK], command, sizeof(ccs.clientCommands[0]));
|
|
Con_DPrintf("Sending %s\n", command);
|
|
}
|
|
|
|
void CLQ3_SendCmd(usercmd_t *cmd)
|
|
{
|
|
char *string;
|
|
int i;
|
|
char data[MAX_OVERALLMSGLEN];
|
|
sizebuf_t msg;
|
|
outframe_t *frame, *oldframe;
|
|
int cmdcount, key;
|
|
usercmd_t *to;
|
|
const usercmd_t *from;
|
|
extern cvar_t cl_nodelta, cl_c2sdupe;
|
|
|
|
//reuse the q1 array
|
|
cmd->servertime = cl.servertime*1000;
|
|
cmd->weapon = ccs.selected_weapon;
|
|
|
|
cmd->forwardmove *= 127/400.0f;
|
|
cmd->sidemove *= 127/400.0f;
|
|
cmd->upmove *= 127/400.0f;
|
|
if (cmd->forwardmove > 127)
|
|
cmd->forwardmove = 127;
|
|
if (cmd->forwardmove < -127)
|
|
cmd->forwardmove = -127;
|
|
if (cmd->sidemove > 127)
|
|
cmd->sidemove = 127;
|
|
if (cmd->sidemove < -127)
|
|
cmd->sidemove = -127;
|
|
if (cmd->upmove > 127)
|
|
cmd->upmove = 127;
|
|
if (cmd->upmove < -127)
|
|
cmd->upmove = -127;
|
|
|
|
if (cmd->buttons & 2) //jump
|
|
{
|
|
cmd->upmove = 100;
|
|
cmd->buttons &= ~2;
|
|
}
|
|
if (Key_Dest_Has(~kdm_game))
|
|
cmd->buttons |= 2; //add in the 'at console' button
|
|
|
|
cl.outframes[cl.movesequence&Q3CMD_MASK].cmd[0] = *cmd;
|
|
cl.movesequence++;
|
|
|
|
//FIXME: q3 generates a new command every video frame, but a new packet at a more limited rate.
|
|
//FIXME: we should return here if its not yet time for a network frame.
|
|
|
|
frame = &cl.outframes[cls.netchan.outgoing_sequence & Q3CMD_MASK];
|
|
frame->cmd_sequence = cl.movesequence;
|
|
frame->server_message_num = ccs.serverMessageNum;
|
|
frame->server_time = cl.gametime;
|
|
frame->client_time = Sys_DoubleTime()*1000;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.maxsize = sizeof(data);
|
|
msg.data = data;
|
|
msg.packing = SZ_HUFFMAN;
|
|
|
|
MSG_WriteBits(&msg, cl.servercount, 32);
|
|
MSG_WriteBits(&msg, ccs.serverMessageNum, 32);
|
|
MSG_WriteBits(&msg, ccs.lastServerCommandNum, 32);
|
|
|
|
// write clientCommands not acknowledged by server yet
|
|
for (i=ccs.lastClientCommandNum+1; i<=ccs.numClientCommands; i++)
|
|
{
|
|
MSG_WriteBits(&msg, clcq3_clientCommand, 8);
|
|
MSG_WriteBits(&msg, i, 32);
|
|
string = ccs.clientCommands[i & Q3TEXTCMD_MASK];
|
|
while(*string)
|
|
MSG_WriteBits(&msg, *string++, 8);
|
|
MSG_WriteBits(&msg, 0, 8);
|
|
}
|
|
|
|
i = cls.netchan.outgoing_sequence;
|
|
i -= bound(0, cl_c2sdupe.ival, 5); //extra age, if desired
|
|
i--;
|
|
if (i < cls.netchan.outgoing_sequence-Q3CMD_MASK)
|
|
i = cls.netchan.outgoing_sequence-Q3CMD_MASK;
|
|
oldframe = &cl.outframes[i & Q3CMD_MASK];
|
|
cmdcount = cl.movesequence - oldframe->cmd_sequence;
|
|
if (cmdcount > Q3CMD_MASK)
|
|
cmdcount = Q3CMD_MASK;
|
|
|
|
// begin a client move command, if any
|
|
if (cmdcount)
|
|
{
|
|
if(cl_nodelta.value || !ccs.snap.valid ||
|
|
ccs.snap.serverMessageNum != ccs.serverMessageNum)
|
|
MSG_WriteBits(&msg, clcq3_nodeltaMove, 8); // no compression
|
|
else
|
|
MSG_WriteBits(&msg, clcq3_move, 8);
|
|
|
|
// write cmdcount
|
|
MSG_WriteBits(&msg, cmdcount, 8);
|
|
|
|
// calculate key
|
|
string = ccs.serverCommands[ccs.lastServerCommandNum & Q3TEXTCMD_MASK];
|
|
key = ccs.fs_key ^ ccs.serverMessageNum ^ StringKey(string, 32);
|
|
|
|
//note that q3 uses timestamps so sequences are not important
|
|
//we can also send dupes without issue.
|
|
from = &nullcmd;
|
|
for (i = cl.movesequence-cmdcount; i < cl.movesequence; i++)
|
|
{
|
|
to = &cl.outframes[i&Q3CMD_MASK].cmd[0];
|
|
MSG_Q3_WriteDeltaUsercmd( &msg, key, from, to );
|
|
from = to;
|
|
}
|
|
}
|
|
|
|
MSG_WriteBits(&msg, clcq3_eom, 8);
|
|
|
|
CL_Netchan_Transmit( msg.cursize, msg.data );
|
|
while(cls.netchan.reliable_length)
|
|
Netchan_TransmitNextFragment(&cls.netchan);
|
|
}
|
|
|
|
void CLQ3_SendAuthPacket(netadr_t *gameserver)
|
|
{
|
|
#ifdef HAVE_PACKET
|
|
char data[2048];
|
|
sizebuf_t msg;
|
|
|
|
//send the auth packet
|
|
//this should be the right code, but it doesn't work.
|
|
if (gameserver->type == NA_IP)
|
|
{
|
|
char *key = Cvar_Get("cl_cdkey", "", 0, "Quake3 auth")->string;
|
|
netadr_t authaddr;
|
|
#define Q3_AUTHORIZE_SERVER_NAME "authorize.quake3arena.com:27952"
|
|
if (*key)
|
|
{
|
|
Con_Printf("Resolving %s\n", Q3_AUTHORIZE_SERVER_NAME);
|
|
if (NET_StringToAdr(Q3_AUTHORIZE_SERVER_NAME, 0, &authaddr))
|
|
{
|
|
msg.data = data;
|
|
msg.cursize = 0;
|
|
msg.overflowed = msg.allowoverflow = 0;
|
|
msg.maxsize = sizeof(data);
|
|
MSG_WriteLong(&msg, -1);
|
|
MSG_WriteString(&msg, "getKeyAuthorize 0 ");
|
|
msg.cursize--;
|
|
while(*key)
|
|
{
|
|
if ((*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z') || (*key >= '0' && *key <= '9'))
|
|
MSG_WriteByte(&msg, *key);
|
|
key++;
|
|
}
|
|
MSG_WriteByte(&msg, 0);
|
|
|
|
NET_SendPacket (cls.sockets, msg.cursize, msg.data, &authaddr);
|
|
}
|
|
else
|
|
Con_Printf(" failed\n");
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CLQ3_SendConnectPacket(netadr_t *to, int challenge, int qport)
|
|
{
|
|
char infostr[1024];
|
|
char data[2048];
|
|
sizebuf_t msg;
|
|
static const char *nonq3[] = {"challenge", "qport", "protocol", "ip", "chat", NULL};
|
|
|
|
memset(&ccs, 0, sizeof(ccs));
|
|
|
|
InfoBuf_ToString(&cls.userinfo[0], infostr, sizeof(infostr), basicuserinfos, nonq3, NULL, &cls.userinfosync, &cls.userinfo[0]);
|
|
|
|
cl.splitclients = 1;
|
|
msg.data = data;
|
|
msg.cursize = 0;
|
|
msg.overflowed = msg.allowoverflow = 0;
|
|
msg.maxsize = sizeof(data);
|
|
MSG_WriteLong(&msg, -1);
|
|
MSG_WriteString(&msg, va("connect \"\\challenge\\%i\\qport\\%i\\protocol\\%i%s\"", challenge, qport, PROTOCOL_VERSION_Q3, infostr));
|
|
#ifdef HUFFNETWORK
|
|
Huff_EncryptPacket(&msg, 12);
|
|
if (!Huff_CompressionCRC(HUFFCRC_QUAKE3))
|
|
{
|
|
Con_Printf("Huffman compression error\n");
|
|
return;
|
|
}
|
|
#endif
|
|
NET_SendPacket (cls.sockets, msg.cursize, msg.data, to);
|
|
}
|
|
#endif
|
|
|