mirror of
https://git.code.sf.net/p/quake/quakeforge-old
synced 2024-11-29 23:21:58 +00:00
3f74fe38b8
default game name from "id1" to "base". This allows you to have full and shareware version's of Id's pak's installed, as well as other full TC's you want.. You may want to make base a symlink to id1 for now since you currently can't have multiple -game's, that's my next fix.
1771 lines
42 KiB
C
1771 lines
42 KiB
C
/*
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
Portions Copyright (C) 1999,2000 Nelson Rush.
|
|
Copyright (C) 1999,2000 contributors of the QuakeForge project
|
|
Please see the file "AUTHORS" for a list of contributors
|
|
|
|
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 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.
|
|
|
|
*/
|
|
// sv_user.c -- server code for moving users
|
|
|
|
#include "qwsvdef.h"
|
|
|
|
edict_t *sv_player;
|
|
|
|
usercmd_t cmd;
|
|
|
|
cvar_t cl_rollspeed = {"cl_rollspeed", "200"};
|
|
cvar_t cl_rollangle = {"cl_rollangle", "2.0"};
|
|
cvar_t sv_spectalk = {"sv_spectalk", "1"};
|
|
|
|
cvar_t sv_mapcheck = {"sv_mapcheck", "1"};
|
|
|
|
extern vec3_t player_mins;
|
|
|
|
extern int fp_messages, fp_persecond, fp_secondsdead;
|
|
extern char fp_msg[];
|
|
extern cvar_t pausable;
|
|
static int loss;
|
|
|
|
/*
|
|
============================================================
|
|
|
|
USER STRINGCMD EXECUTION
|
|
|
|
host_client and sv_player will be valid.
|
|
============================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
SV_New_f
|
|
|
|
Sends the first message from the server to a connected client.
|
|
This will be sent on the initial connection and upon each server load.
|
|
================
|
|
*/
|
|
void SV_New_f (void)
|
|
{
|
|
char *gamedir;
|
|
int playernum;
|
|
|
|
if (host_client->state == cs_spawned)
|
|
return;
|
|
|
|
host_client->state = cs_connected;
|
|
host_client->connection_started = realtime;
|
|
|
|
// send the info about the new client to all connected clients
|
|
// SV_FullClientUpdate (host_client, &sv.reliable_datagram);
|
|
// host_client->sendinfo = true;
|
|
|
|
gamedir = Info_ValueForKey (svs.info, "*gamedir");
|
|
if (!gamedir[0])
|
|
gamedir = "qw";
|
|
|
|
//NOTE: This doesn't go through ClientReliableWrite since it's before the user
|
|
//spawns. These functions are written to not overflow
|
|
if (host_client->num_backbuf) {
|
|
Con_Printf("WARNING %s: [SV_New] Back buffered (%d0, clearing", host_client->name, host_client->netchan.message.cursize);
|
|
host_client->num_backbuf = 0;
|
|
SZ_Clear(&host_client->netchan.message);
|
|
}
|
|
|
|
// send the serverdata
|
|
MSG_WriteByte (&host_client->netchan.message, svc_serverdata);
|
|
MSG_WriteLong (&host_client->netchan.message, PROTOCOL_VERSION);
|
|
MSG_WriteLong (&host_client->netchan.message, svs.spawncount);
|
|
MSG_WriteString (&host_client->netchan.message, gamedir);
|
|
|
|
playernum = NUM_FOR_EDICT(host_client->edict)-1;
|
|
if (host_client->spectator)
|
|
playernum |= 128;
|
|
MSG_WriteByte (&host_client->netchan.message, playernum);
|
|
|
|
// send full levelname
|
|
MSG_WriteString (&host_client->netchan.message, PR_GetString(sv.edicts->v.message));
|
|
|
|
// send the movevars
|
|
MSG_WriteFloat(&host_client->netchan.message, movevars.gravity);
|
|
MSG_WriteFloat(&host_client->netchan.message, movevars.stopspeed);
|
|
MSG_WriteFloat(&host_client->netchan.message, movevars.maxspeed);
|
|
MSG_WriteFloat(&host_client->netchan.message, movevars.spectatormaxspeed);
|
|
MSG_WriteFloat(&host_client->netchan.message, movevars.accelerate);
|
|
MSG_WriteFloat(&host_client->netchan.message, movevars.airaccelerate);
|
|
MSG_WriteFloat(&host_client->netchan.message, movevars.wateraccelerate);
|
|
MSG_WriteFloat(&host_client->netchan.message, movevars.friction);
|
|
MSG_WriteFloat(&host_client->netchan.message, movevars.waterfriction);
|
|
MSG_WriteFloat(&host_client->netchan.message, movevars.entgravity);
|
|
|
|
// send music
|
|
MSG_WriteByte (&host_client->netchan.message, svc_cdtrack);
|
|
MSG_WriteByte (&host_client->netchan.message, sv.edicts->v.sounds);
|
|
|
|
// send server info string
|
|
MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&host_client->netchan.message, va("fullserverinfo \"%s\"\n", svs.info) );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Soundlist_f
|
|
==================
|
|
*/
|
|
void SV_Soundlist_f (void)
|
|
{
|
|
char **s;
|
|
int n;
|
|
|
|
if (host_client->state != cs_connected)
|
|
{
|
|
Con_Printf ("soundlist not valid -- allready spawned\n");
|
|
return;
|
|
}
|
|
|
|
// handle the case of a level changing while a client was connecting
|
|
if ( atoi(Cmd_Argv(1)) != svs.spawncount )
|
|
{
|
|
Con_Printf ("SV_Soundlist_f from different level\n");
|
|
SV_New_f ();
|
|
return;
|
|
}
|
|
|
|
n = atoi(Cmd_Argv(2));
|
|
|
|
//NOTE: This doesn't go through ClientReliableWrite since it's before the user
|
|
//spawns. These functions are written to not overflow
|
|
if (host_client->num_backbuf) {
|
|
Con_Printf("WARNING %s: [SV_Soundlist] Back buffered (%d0, clearing", host_client->name, host_client->netchan.message.cursize);
|
|
host_client->num_backbuf = 0;
|
|
SZ_Clear(&host_client->netchan.message);
|
|
}
|
|
|
|
MSG_WriteByte (&host_client->netchan.message, svc_soundlist);
|
|
MSG_WriteByte (&host_client->netchan.message, n);
|
|
for (s = sv.sound_precache+1 + n ;
|
|
*s && host_client->netchan.message.cursize < (MAX_MSGLEN/2);
|
|
s++, n++)
|
|
MSG_WriteString (&host_client->netchan.message, *s);
|
|
|
|
MSG_WriteByte (&host_client->netchan.message, 0);
|
|
|
|
// next msg
|
|
if (*s)
|
|
MSG_WriteByte (&host_client->netchan.message, n);
|
|
else
|
|
MSG_WriteByte (&host_client->netchan.message, 0);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Modellist_f
|
|
==================
|
|
*/
|
|
void SV_Modellist_f (void)
|
|
{
|
|
char **s;
|
|
int n;
|
|
|
|
if (host_client->state != cs_connected)
|
|
{
|
|
Con_Printf ("modellist not valid -- allready spawned\n");
|
|
return;
|
|
}
|
|
|
|
// handle the case of a level changing while a client was connecting
|
|
if ( atoi(Cmd_Argv(1)) != svs.spawncount )
|
|
{
|
|
Con_Printf ("SV_Modellist_f from different level\n");
|
|
SV_New_f ();
|
|
return;
|
|
}
|
|
|
|
n = atoi(Cmd_Argv(2));
|
|
|
|
//NOTE: This doesn't go through ClientReliableWrite since it's before the user
|
|
//spawns. These functions are written to not overflow
|
|
if (host_client->num_backbuf) {
|
|
Con_Printf("WARNING %s: [SV_Modellist] Back buffered (%d0, clearing", host_client->name, host_client->netchan.message.cursize);
|
|
host_client->num_backbuf = 0;
|
|
SZ_Clear(&host_client->netchan.message);
|
|
}
|
|
|
|
MSG_WriteByte (&host_client->netchan.message, svc_modellist);
|
|
MSG_WriteByte (&host_client->netchan.message, n);
|
|
for (s = sv.model_precache+1+n ;
|
|
*s && host_client->netchan.message.cursize < (MAX_MSGLEN/2);
|
|
s++, n++)
|
|
MSG_WriteString (&host_client->netchan.message, *s);
|
|
MSG_WriteByte (&host_client->netchan.message, 0);
|
|
|
|
// next msg
|
|
if (*s)
|
|
MSG_WriteByte (&host_client->netchan.message, n);
|
|
else
|
|
MSG_WriteByte (&host_client->netchan.message, 0);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_PreSpawn_f
|
|
==================
|
|
*/
|
|
void SV_PreSpawn_f (void)
|
|
{
|
|
unsigned buf;
|
|
unsigned check;
|
|
|
|
if (host_client->state != cs_connected)
|
|
{
|
|
Con_Printf ("prespawn not valid -- allready spawned\n");
|
|
return;
|
|
}
|
|
|
|
// handle the case of a level changing while a client was connecting
|
|
if ( atoi(Cmd_Argv(1)) != svs.spawncount )
|
|
{
|
|
Con_Printf ("SV_PreSpawn_f from different level\n");
|
|
SV_New_f ();
|
|
return;
|
|
}
|
|
|
|
buf = atoi(Cmd_Argv(2));
|
|
if (buf >= sv.num_signon_buffers)
|
|
buf = 0;
|
|
|
|
if (!buf) {
|
|
// should be three numbers following containing checksums
|
|
check = atoi(Cmd_Argv(3));
|
|
|
|
// Con_DPrintf("Client check = %d\n", check);
|
|
|
|
if (sv_mapcheck.value && check != sv.worldmodel->checksum &&
|
|
check != sv.worldmodel->checksum2) {
|
|
SV_ClientPrintf (host_client, PRINT_HIGH,
|
|
"Map model file does not match (%s), %i != %i/%i.\n"
|
|
"You may need a new version of the map, or the proper install files.\n",
|
|
sv.modelname, check, sv.worldmodel->checksum, sv.worldmodel->checksum2);
|
|
SV_DropClient (host_client);
|
|
return;
|
|
}
|
|
host_client->checksum = check;
|
|
}
|
|
|
|
//NOTE: This doesn't go through ClientReliableWrite since it's before the user
|
|
//spawns. These functions are written to not overflow
|
|
if (host_client->num_backbuf) {
|
|
Con_Printf("WARNING %s: [SV_PreSpawn] Back buffered (%d0, clearing", host_client->name, host_client->netchan.message.cursize);
|
|
host_client->num_backbuf = 0;
|
|
SZ_Clear(&host_client->netchan.message);
|
|
}
|
|
|
|
SZ_Write (&host_client->netchan.message,
|
|
sv.signon_buffers[buf],
|
|
sv.signon_buffer_size[buf]);
|
|
|
|
buf++;
|
|
if (buf == sv.num_signon_buffers)
|
|
{ // all done prespawning
|
|
MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&host_client->netchan.message, va("cmd spawn %i 0\n",svs.spawncount) );
|
|
}
|
|
else
|
|
{ // need to prespawn more
|
|
MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&host_client->netchan.message,
|
|
va("cmd prespawn %i %i\n", svs.spawncount, buf) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Spawn_f
|
|
==================
|
|
*/
|
|
void SV_Spawn_f (void)
|
|
{
|
|
int i;
|
|
client_t *client;
|
|
edict_t *ent;
|
|
eval_t *val;
|
|
int n;
|
|
|
|
if (host_client->state != cs_connected)
|
|
{
|
|
Con_Printf ("Spawn not valid -- allready spawned\n");
|
|
return;
|
|
}
|
|
|
|
// handle the case of a level changing while a client was connecting
|
|
if ( atoi(Cmd_Argv(1)) != svs.spawncount )
|
|
{
|
|
Con_Printf ("SV_Spawn_f from different level\n");
|
|
SV_New_f ();
|
|
return;
|
|
}
|
|
|
|
n = atoi(Cmd_Argv(2));
|
|
|
|
// make sure n is valid
|
|
if ( n < 0 || n > MAX_CLIENTS )
|
|
{
|
|
Con_Printf ("SV_Spawn_f invalid client start\n");
|
|
SV_New_f ();
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
// send all current names, colors, and frag counts
|
|
// FIXME: is this a good thing?
|
|
SZ_Clear (&host_client->netchan.message);
|
|
|
|
// send current status of all other players
|
|
|
|
// normally this could overflow, but no need to check due to backbuf
|
|
for (i=n, client = svs.clients + n ; i<MAX_CLIENTS ; i++, client++)
|
|
SV_FullClientUpdateToClient (client, host_client);
|
|
|
|
// send all current light styles
|
|
for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
|
|
{
|
|
ClientReliableWrite_Begin (host_client, svc_lightstyle,
|
|
3 + (sv.lightstyles[i] ? strlen(sv.lightstyles[i]) : 1));
|
|
ClientReliableWrite_Byte (host_client, (char)i);
|
|
ClientReliableWrite_String (host_client, sv.lightstyles[i]);
|
|
}
|
|
|
|
// set up the edict
|
|
ent = host_client->edict;
|
|
|
|
memset (&ent->v, 0, progs->entityfields * 4);
|
|
ent->v.colormap = NUM_FOR_EDICT(ent);
|
|
ent->v.team = 0; // FIXME
|
|
ent->v.netname = PR_SetString(host_client->name);
|
|
|
|
host_client->entgravity = 1.0;
|
|
val = GetEdictFieldValue(ent, "gravity");
|
|
if (val)
|
|
val->_float = 1.0;
|
|
host_client->maxspeed = sv_maxspeed.value;
|
|
val = GetEdictFieldValue(ent, "maxspeed");
|
|
if (val)
|
|
val->_float = sv_maxspeed.value;
|
|
|
|
//
|
|
// force stats to be updated
|
|
//
|
|
memset (host_client->stats, 0, sizeof(host_client->stats));
|
|
|
|
ClientReliableWrite_Begin (host_client, svc_updatestatlong, 6);
|
|
ClientReliableWrite_Byte (host_client, STAT_TOTALSECRETS);
|
|
ClientReliableWrite_Long (host_client, pr_global_struct->total_secrets);
|
|
|
|
ClientReliableWrite_Begin (host_client, svc_updatestatlong, 6);
|
|
ClientReliableWrite_Byte (host_client, STAT_TOTALMONSTERS);
|
|
ClientReliableWrite_Long (host_client, pr_global_struct->total_monsters);
|
|
|
|
ClientReliableWrite_Begin (host_client, svc_updatestatlong, 6);
|
|
ClientReliableWrite_Byte (host_client, STAT_SECRETS);
|
|
ClientReliableWrite_Long (host_client, pr_global_struct->found_secrets);
|
|
|
|
ClientReliableWrite_Begin (host_client, svc_updatestatlong, 6);
|
|
ClientReliableWrite_Byte (host_client, STAT_MONSTERS);
|
|
ClientReliableWrite_Long (host_client, pr_global_struct->killed_monsters);
|
|
|
|
// get the client to check and download skins
|
|
// when that is completed, a begin command will be issued
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
|
|
ClientReliableWrite_String (host_client, "skins\n" );
|
|
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_SpawnSpectator
|
|
==================
|
|
*/
|
|
void SV_SpawnSpectator (void)
|
|
{
|
|
int i;
|
|
edict_t *e;
|
|
|
|
VectorCopy (vec3_origin, sv_player->v.origin);
|
|
VectorCopy (vec3_origin, sv_player->v.view_ofs);
|
|
sv_player->v.view_ofs[2] = 22;
|
|
|
|
// search for an info_playerstart to spawn the spectator at
|
|
for (i=MAX_CLIENTS-1 ; i<sv.num_edicts ; i++)
|
|
{
|
|
e = EDICT_NUM(i);
|
|
if (!strcmp(PR_GetString(e->v.classname), "info_player_start"))
|
|
{
|
|
VectorCopy (e->v.origin, sv_player->v.origin);
|
|
return;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Begin_f
|
|
==================
|
|
*/
|
|
void SV_Begin_f (void)
|
|
{
|
|
unsigned pmodel = 0, emodel = 0;
|
|
int i;
|
|
|
|
if (host_client->state == cs_spawned)
|
|
return; // don't begin again
|
|
|
|
host_client->state = cs_spawned;
|
|
|
|
// handle the case of a level changing while a client was connecting
|
|
if ( atoi(Cmd_Argv(1)) != svs.spawncount )
|
|
{
|
|
Con_Printf ("SV_Begin_f from different level\n");
|
|
SV_New_f ();
|
|
return;
|
|
}
|
|
|
|
if (host_client->spectator)
|
|
{
|
|
SV_SpawnSpectator ();
|
|
|
|
if (SpectatorConnect) {
|
|
// copy spawn parms out of the client_t
|
|
for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
|
|
(&pr_global_struct->parm1)[i] = host_client->spawn_parms[i];
|
|
|
|
// call the spawn function
|
|
pr_global_struct->time = sv.time;
|
|
pr_global_struct->self = EDICT_TO_PROG(sv_player);
|
|
PR_ExecuteProgram (SpectatorConnect);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// copy spawn parms out of the client_t
|
|
for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
|
|
(&pr_global_struct->parm1)[i] = host_client->spawn_parms[i];
|
|
|
|
// call the spawn function
|
|
pr_global_struct->time = sv.time;
|
|
pr_global_struct->self = EDICT_TO_PROG(sv_player);
|
|
PR_ExecuteProgram (pr_global_struct->ClientConnect);
|
|
|
|
// actually spawn the player
|
|
pr_global_struct->time = sv.time;
|
|
pr_global_struct->self = EDICT_TO_PROG(sv_player);
|
|
PR_ExecuteProgram (pr_global_struct->PutClientInServer);
|
|
}
|
|
|
|
// clear the net statistics, because connecting gives a bogus picture
|
|
host_client->netchan.frame_latency = 0;
|
|
host_client->netchan.frame_rate = 0;
|
|
host_client->netchan.drop_count = 0;
|
|
host_client->netchan.good_count = 0;
|
|
|
|
//check he's not cheating
|
|
|
|
pmodel = atoi(Info_ValueForKey (host_client->userinfo, "pmodel"));
|
|
emodel = atoi(Info_ValueForKey (host_client->userinfo, "emodel"));
|
|
|
|
if (pmodel != sv.model_player_checksum ||
|
|
emodel != sv.eyes_player_checksum)
|
|
SV_BroadcastPrintf (PRINT_HIGH, "%s WARNING: non standard player/eyes model detected\n", host_client->name);
|
|
|
|
// if we are paused, tell the client
|
|
if (sv.paused) {
|
|
ClientReliableWrite_Begin (host_client, svc_setpause, 2);
|
|
ClientReliableWrite_Byte (host_client, sv.paused);
|
|
SV_ClientPrintf(host_client, PRINT_HIGH, "Server is paused.\n");
|
|
}
|
|
|
|
#if 0
|
|
//
|
|
// send a fixangle over the reliable channel to make sure it gets there
|
|
// Never send a roll angle, because savegames can catch the server
|
|
// in a state where it is expecting the client to correct the angle
|
|
// and it won't happen if the game was just loaded, so you wind up
|
|
// with a permanent head tilt
|
|
ent = EDICT_NUM( 1 + (host_client - svs.clients) );
|
|
MSG_WriteByte (&host_client->netchan.message, svc_setangle);
|
|
for (i=0 ; i < 2 ; i++)
|
|
MSG_WriteAngle (&host_client->netchan.message, ent->v.angles[i] );
|
|
MSG_WriteAngle (&host_client->netchan.message, 0 );
|
|
#endif
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
==================
|
|
SV_NextDownload_f
|
|
==================
|
|
*/
|
|
void SV_NextDownload_f (void)
|
|
{
|
|
byte buffer[1024];
|
|
int r;
|
|
int percent;
|
|
int size;
|
|
|
|
if (!host_client->download)
|
|
return;
|
|
|
|
r = host_client->downloadsize - host_client->downloadcount;
|
|
if (r > 768)
|
|
r = 768;
|
|
r = fread (buffer, 1, r, host_client->download);
|
|
ClientReliableWrite_Begin (host_client, svc_download, 6+r);
|
|
ClientReliableWrite_Short (host_client, r);
|
|
|
|
host_client->downloadcount += r;
|
|
size = host_client->downloadsize;
|
|
if (!size)
|
|
size = 1;
|
|
percent = host_client->downloadcount*100/size;
|
|
ClientReliableWrite_Byte (host_client, percent);
|
|
ClientReliableWrite_SZ (host_client, buffer, r);
|
|
|
|
if (host_client->downloadcount != host_client->downloadsize)
|
|
return;
|
|
|
|
fclose (host_client->download);
|
|
host_client->download = NULL;
|
|
|
|
}
|
|
|
|
void OutofBandPrintf(netadr_t where, char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
char send[1024];
|
|
|
|
send[0] = 0xff;
|
|
send[1] = 0xff;
|
|
send[2] = 0xff;
|
|
send[3] = 0xff;
|
|
send[4] = A2C_PRINT;
|
|
va_start (argptr, fmt);
|
|
vsnprintf (send+5, sizeof(send) - 5, fmt, argptr);
|
|
va_end (argptr);
|
|
|
|
NET_SendPacket (strlen(send)+1, send, where);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_NextUpload
|
|
==================
|
|
*/
|
|
void SV_NextUpload (void)
|
|
{
|
|
int percent;
|
|
int size;
|
|
|
|
if (!*host_client->uploadfn) {
|
|
SV_ClientPrintf(host_client, PRINT_HIGH, "Upload denied\n");
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
|
|
ClientReliableWrite_String (host_client, "stopul");
|
|
|
|
// suck out rest of packet
|
|
size = MSG_ReadShort (); MSG_ReadByte ();
|
|
msg_readcount += size;
|
|
return;
|
|
}
|
|
|
|
size = MSG_ReadShort ();
|
|
percent = MSG_ReadByte ();
|
|
|
|
if (!host_client->upload)
|
|
{
|
|
host_client->upload = fopen(host_client->uploadfn, "wb");
|
|
if (!host_client->upload) {
|
|
Sys_Printf("Can't create %s\n", host_client->uploadfn);
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
|
|
ClientReliableWrite_String (host_client, "stopul");
|
|
*host_client->uploadfn = 0;
|
|
return;
|
|
}
|
|
Sys_Printf("Receiving %s from %d...\n", host_client->uploadfn, host_client->userid);
|
|
if (host_client->remote_snap)
|
|
OutofBandPrintf(host_client->snap_from, "Server receiving %s from %d...\n", host_client->uploadfn, host_client->userid);
|
|
}
|
|
|
|
fwrite (net_message.data + msg_readcount, 1, size, host_client->upload);
|
|
msg_readcount += size;
|
|
|
|
Con_DPrintf ("UPLOAD: %d received\n", size);
|
|
|
|
if (percent != 100) {
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
|
|
ClientReliableWrite_String (host_client, "nextul\n");
|
|
} else {
|
|
fclose (host_client->upload);
|
|
host_client->upload = NULL;
|
|
|
|
Sys_Printf("%s upload completed.\n", host_client->uploadfn);
|
|
|
|
if (host_client->remote_snap) {
|
|
char *p;
|
|
|
|
if ((p = strchr(host_client->uploadfn, '/')) != NULL)
|
|
p++;
|
|
else
|
|
p = host_client->uploadfn;
|
|
OutofBandPrintf(host_client->snap_from, "%s upload completed.\nTo download, enter:\ndownload %s\n",
|
|
host_client->uploadfn, p);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_BeginDownload_f
|
|
==================
|
|
*/
|
|
void SV_BeginDownload_f(void)
|
|
{
|
|
char *name;
|
|
extern cvar_t allow_download;
|
|
extern cvar_t allow_download_skins;
|
|
extern cvar_t allow_download_models;
|
|
extern cvar_t allow_download_sounds;
|
|
extern cvar_t allow_download_maps;
|
|
extern int file_from_pak; // ZOID did file come from pak?
|
|
|
|
name = Cmd_Argv(1);
|
|
// hacked by zoid to allow more conrol over download
|
|
// first off, no .. or global allow check
|
|
if (strstr (name, "..") || !allow_download.value
|
|
// leading dot is no good
|
|
|| *name == '.'
|
|
// leading slash bad as well, must be in subdir
|
|
|| *name == '/'
|
|
// next up, skin check
|
|
|| (strncmp(name, "skins/", 6) == 0 && !allow_download_skins.value)
|
|
// now models
|
|
|| (strncmp(name, "progs/", 6) == 0 && !allow_download_models.value)
|
|
// now sounds
|
|
|| (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds.value)
|
|
// now maps (note special case for maps, must not be in pak)
|
|
|| (strncmp(name, "maps/", 6) == 0 && !allow_download_maps.value)
|
|
// MUST be in a subdirectory
|
|
|| !strstr (name, "/") )
|
|
{ // don't allow anything with .. path
|
|
ClientReliableWrite_Begin (host_client, svc_download, 4);
|
|
ClientReliableWrite_Short (host_client, -1);
|
|
ClientReliableWrite_Byte (host_client, 0);
|
|
return;
|
|
}
|
|
|
|
if (host_client->download) {
|
|
fclose (host_client->download);
|
|
host_client->download = NULL;
|
|
}
|
|
|
|
// lowercase name (needed for casesen file systems)
|
|
{
|
|
char *p;
|
|
|
|
for (p = name; *p; p++)
|
|
*p = (char)tolower(*p);
|
|
}
|
|
|
|
|
|
host_client->downloadsize = COM_FOpenFile (name, &host_client->download);
|
|
host_client->downloadcount = 0;
|
|
|
|
if (!host_client->download
|
|
// special check for maps, if it came from a pak file, don't allow
|
|
// download ZOID
|
|
|| (strncmp(name, "maps/", 5) == 0 && file_from_pak))
|
|
{
|
|
if (host_client->download) {
|
|
fclose(host_client->download);
|
|
host_client->download = NULL;
|
|
}
|
|
|
|
Sys_Printf ("Couldn't download %s to %s\n", name, host_client->name);
|
|
ClientReliableWrite_Begin (host_client, svc_download, 4);
|
|
ClientReliableWrite_Short (host_client, -1);
|
|
ClientReliableWrite_Byte (host_client, 0);
|
|
return;
|
|
}
|
|
|
|
SV_NextDownload_f ();
|
|
Sys_Printf ("Downloading %s to %s\n", name, host_client->name);
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
==================
|
|
SV_Say
|
|
==================
|
|
*/
|
|
void SV_Say (qboolean team)
|
|
{
|
|
client_t *client;
|
|
int j, tmp;
|
|
char *p;
|
|
char text[2048];
|
|
char t1[32], *t2;
|
|
|
|
if (Cmd_Argc () < 2)
|
|
return;
|
|
|
|
if (team)
|
|
{
|
|
strncpy (t1, Info_ValueForKey (host_client->userinfo, "team"), 31);
|
|
t1[31] = 0;
|
|
}
|
|
|
|
if (host_client->spectator && (!sv_spectalk.value || team))
|
|
snprintf(text, sizeof(text), "[SPEC] %s: ", host_client->name);
|
|
else if (team)
|
|
snprintf(text, sizeof(text), "(%s): ", host_client->name);
|
|
else {
|
|
snprintf(text, sizeof(text), "%s: ", host_client->name);
|
|
}
|
|
|
|
if (fp_messages) {
|
|
if (!sv.paused && realtime<host_client->lockedtill) {
|
|
SV_ClientPrintf(host_client, PRINT_CHAT,
|
|
"You can't talk for %d more seconds\n",
|
|
(int) (host_client->lockedtill - realtime));
|
|
return;
|
|
}
|
|
tmp = host_client->whensaidhead - fp_messages + 1;
|
|
if (tmp < 0)
|
|
tmp = 10+tmp;
|
|
if (!sv.paused &&
|
|
host_client->whensaid[tmp] && (realtime-host_client->whensaid[tmp] < fp_persecond)) {
|
|
host_client->lockedtill = realtime + fp_secondsdead;
|
|
if (fp_msg[0])
|
|
SV_ClientPrintf(host_client, PRINT_CHAT,
|
|
"FloodProt: %s\n", fp_msg);
|
|
else
|
|
SV_ClientPrintf(host_client, PRINT_CHAT,
|
|
"FloodProt: You can't talk for %d seconds.\n", fp_secondsdead);
|
|
return;
|
|
}
|
|
host_client->whensaidhead++;
|
|
if (host_client->whensaidhead > 9)
|
|
host_client->whensaidhead = 0;
|
|
host_client->whensaid[host_client->whensaidhead] = realtime;
|
|
}
|
|
|
|
p = Cmd_Args();
|
|
|
|
if (*p == '"')
|
|
{
|
|
p++;
|
|
p[Q_strlen(p)-1] = 0;
|
|
}
|
|
|
|
Q_strcat(text, p);
|
|
Q_strcat(text, "\n");
|
|
|
|
Sys_Printf ("%s", text);
|
|
|
|
for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
|
|
{
|
|
if (client->state != cs_spawned)
|
|
continue;
|
|
if (host_client->spectator && !sv_spectalk.value)
|
|
if (!client->spectator)
|
|
continue;
|
|
|
|
if (team)
|
|
{
|
|
// the spectator team
|
|
if (host_client->spectator) {
|
|
if (!client->spectator)
|
|
continue;
|
|
} else {
|
|
t2 = Info_ValueForKey (client->userinfo, "team");
|
|
if (strcmp(t1, t2) || client->spectator)
|
|
continue; // on different teams
|
|
}
|
|
}
|
|
SV_ClientPrintf(client, PRINT_CHAT, "%s", text);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
SV_Say_f
|
|
==================
|
|
*/
|
|
void SV_Say_f(void)
|
|
{
|
|
SV_Say (false);
|
|
}
|
|
/*
|
|
==================
|
|
SV_Say_Team_f
|
|
==================
|
|
*/
|
|
void SV_Say_Team_f(void)
|
|
{
|
|
SV_Say (true);
|
|
}
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
=================
|
|
SV_Pings_f
|
|
|
|
The client is showing the scoreboard, so send new ping times for all
|
|
clients
|
|
=================
|
|
*/
|
|
void SV_Pings_f (void)
|
|
{
|
|
client_t *client;
|
|
int j;
|
|
|
|
for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
|
|
{
|
|
if (client->state != cs_spawned)
|
|
continue;
|
|
|
|
ClientReliableWrite_Begin (host_client, svc_updateping, 4);
|
|
ClientReliableWrite_Byte (host_client, j);
|
|
ClientReliableWrite_Short (host_client, client->ping);
|
|
ClientReliableWrite_Begin (host_client, svc_updatepl, 4);
|
|
ClientReliableWrite_Byte (host_client, j);
|
|
ClientReliableWrite_Byte (host_client, client->lossage);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
==================
|
|
SV_Kill_f
|
|
==================
|
|
*/
|
|
void SV_Kill_f (void)
|
|
{
|
|
if (sv_player->v.health <= 0)
|
|
{
|
|
SV_ClientPrintf (host_client, PRINT_HIGH, "Can't suicide -- allready dead!\n");
|
|
return;
|
|
}
|
|
|
|
pr_global_struct->time = sv.time;
|
|
pr_global_struct->self = EDICT_TO_PROG(sv_player);
|
|
PR_ExecuteProgram (pr_global_struct->ClientKill);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_TogglePause
|
|
==================
|
|
*/
|
|
void SV_TogglePause (const char *msg)
|
|
{
|
|
int i;
|
|
client_t *cl;
|
|
|
|
sv.paused ^= 1;
|
|
|
|
if (msg)
|
|
SV_BroadcastPrintf (PRINT_HIGH, "%s", msg);
|
|
|
|
// send notification to all clients
|
|
for (i=0, cl = svs.clients ; i<MAX_CLIENTS ; i++, cl++)
|
|
{
|
|
if (!cl->state)
|
|
continue;
|
|
ClientReliableWrite_Begin (cl, svc_setpause, 2);
|
|
ClientReliableWrite_Byte (cl, sv.paused);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
SV_Pause_f
|
|
==================
|
|
*/
|
|
void SV_Pause_f (void)
|
|
{
|
|
char st[sizeof(host_client->name) + 32];
|
|
|
|
if (!pausable.value) {
|
|
SV_ClientPrintf (host_client, PRINT_HIGH, "Pause not allowed.\n");
|
|
return;
|
|
}
|
|
|
|
if (host_client->spectator) {
|
|
SV_ClientPrintf (host_client, PRINT_HIGH, "Spectators can not pause.\n");
|
|
return;
|
|
}
|
|
|
|
if (sv.paused)
|
|
snprintf(st, sizeof(st), "%s paused the game\n", host_client->name);
|
|
else
|
|
snprintf(st, sizeof(st), "%s unpaused the game\n", host_client->name);
|
|
|
|
SV_TogglePause(st);
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
SV_Drop_f
|
|
|
|
The client is going to disconnect, so remove the connection immediately
|
|
=================
|
|
*/
|
|
void SV_Drop_f (void)
|
|
{
|
|
SV_EndRedirect ();
|
|
if (!host_client->spectator)
|
|
SV_BroadcastPrintf (PRINT_HIGH, "%s dropped\n", host_client->name);
|
|
SV_DropClient (host_client);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
SV_PTrack_f
|
|
|
|
Change the bandwidth estimate for a client
|
|
=================
|
|
*/
|
|
void SV_PTrack_f (void)
|
|
{
|
|
int i;
|
|
edict_t *ent, *tent;
|
|
|
|
if (!host_client->spectator)
|
|
return;
|
|
|
|
if (Cmd_Argc() != 2)
|
|
{
|
|
// turn off tracking
|
|
host_client->spec_track = 0;
|
|
ent = EDICT_NUM(host_client - svs.clients + 1);
|
|
tent = EDICT_NUM(0);
|
|
ent->v.goalentity = EDICT_TO_PROG(tent);
|
|
return;
|
|
}
|
|
|
|
i = atoi(Cmd_Argv(1));
|
|
if (i < 0 || i >= MAX_CLIENTS || svs.clients[i].state != cs_spawned ||
|
|
svs.clients[i].spectator) {
|
|
SV_ClientPrintf (host_client, PRINT_HIGH, "Invalid client to track\n");
|
|
host_client->spec_track = 0;
|
|
ent = EDICT_NUM(host_client - svs.clients + 1);
|
|
tent = EDICT_NUM(0);
|
|
ent->v.goalentity = EDICT_TO_PROG(tent);
|
|
return;
|
|
}
|
|
host_client->spec_track = i + 1; // now tracking
|
|
|
|
ent = EDICT_NUM(host_client - svs.clients + 1);
|
|
tent = EDICT_NUM(i + 1);
|
|
ent->v.goalentity = EDICT_TO_PROG(tent);
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
SV_Rate_f
|
|
|
|
Change the bandwidth estimate for a client
|
|
=================
|
|
*/
|
|
void SV_Rate_f (void)
|
|
{
|
|
int rate;
|
|
|
|
if (Cmd_Argc() != 2)
|
|
{
|
|
SV_ClientPrintf (host_client, PRINT_HIGH, "Current rate is %i\n",
|
|
(int)(1.0/host_client->netchan.rate + 0.5));
|
|
return;
|
|
}
|
|
|
|
rate = atoi(Cmd_Argv(1));
|
|
if (rate < 500)
|
|
rate = 500;
|
|
if (rate > 10000)
|
|
rate = 10000;
|
|
|
|
SV_ClientPrintf (host_client, PRINT_HIGH, "Net rate set to %i\n", rate);
|
|
host_client->netchan.rate = 1.0/rate;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
SV_Msg_f
|
|
|
|
Change the message level for a client
|
|
=================
|
|
*/
|
|
void SV_Msg_f (void)
|
|
{
|
|
if (Cmd_Argc() != 2)
|
|
{
|
|
SV_ClientPrintf (host_client, PRINT_HIGH, "Current msg level is %i\n",
|
|
host_client->messagelevel);
|
|
return;
|
|
}
|
|
|
|
host_client->messagelevel = atoi(Cmd_Argv(1));
|
|
|
|
SV_ClientPrintf (host_client, PRINT_HIGH, "Msg level set to %i\n", host_client->messagelevel);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_SetInfo_f
|
|
|
|
Allow clients to change userinfo
|
|
==================
|
|
*/
|
|
void SV_SetInfo_f (void)
|
|
{
|
|
int i;
|
|
char oldval[MAX_INFO_STRING];
|
|
|
|
|
|
if (Cmd_Argc() == 1)
|
|
{
|
|
Con_Printf ("User info settings:\n");
|
|
Info_Print (host_client->userinfo);
|
|
return;
|
|
}
|
|
|
|
if (Cmd_Argc() != 3)
|
|
{
|
|
Con_Printf ("usage: setinfo [ <key> <value> ]\n");
|
|
return;
|
|
}
|
|
|
|
if (Cmd_Argv(1)[0] == '*')
|
|
return; // don't set priveledged values
|
|
|
|
strcpy(oldval, Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)));
|
|
|
|
Info_SetValueForKey (host_client->userinfo, Cmd_Argv(1), Cmd_Argv(2), MAX_INFO_STRING);
|
|
// name is extracted below in ExtractFromUserInfo
|
|
// strncpy (host_client->name, Info_ValueForKey (host_client->userinfo, "name")
|
|
// , sizeof(host_client->name)-1);
|
|
// SV_FullClientUpdate (host_client, &sv.reliable_datagram);
|
|
// host_client->sendinfo = true;
|
|
|
|
if (!strcmp(Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)), oldval))
|
|
return; // key hasn't changed
|
|
|
|
// process any changed values
|
|
SV_ExtractFromUserinfo (host_client);
|
|
|
|
i = host_client - svs.clients;
|
|
MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
|
|
MSG_WriteByte (&sv.reliable_datagram, i);
|
|
MSG_WriteString (&sv.reliable_datagram, Cmd_Argv(1));
|
|
MSG_WriteString (&sv.reliable_datagram, Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)));
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_ShowServerinfo_f
|
|
|
|
Dumps the serverinfo info string
|
|
==================
|
|
*/
|
|
void SV_ShowServerinfo_f (void)
|
|
{
|
|
Info_Print (svs.info);
|
|
}
|
|
|
|
void SV_NoSnap_f(void)
|
|
{
|
|
if (*host_client->uploadfn) {
|
|
*host_client->uploadfn = 0;
|
|
SV_BroadcastPrintf (PRINT_HIGH, "%s refused remote screenshot\n", host_client->name);
|
|
}
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
char *name;
|
|
void (*func) (void);
|
|
} ucmd_t;
|
|
|
|
ucmd_t ucmds[] =
|
|
{
|
|
{"new", SV_New_f},
|
|
{"modellist", SV_Modellist_f},
|
|
{"soundlist", SV_Soundlist_f},
|
|
{"prespawn", SV_PreSpawn_f},
|
|
{"spawn", SV_Spawn_f},
|
|
{"begin", SV_Begin_f},
|
|
|
|
{"drop", SV_Drop_f},
|
|
{"pings", SV_Pings_f},
|
|
|
|
// issued by hand at client consoles
|
|
{"rate", SV_Rate_f},
|
|
{"kill", SV_Kill_f},
|
|
{"pause", SV_Pause_f},
|
|
{"msg", SV_Msg_f},
|
|
|
|
{"say", SV_Say_f},
|
|
{"say_team", SV_Say_Team_f},
|
|
|
|
{"setinfo", SV_SetInfo_f},
|
|
|
|
{"serverinfo", SV_ShowServerinfo_f},
|
|
|
|
{"download", SV_BeginDownload_f},
|
|
{"nextdl", SV_NextDownload_f},
|
|
|
|
{"ptrack", SV_PTrack_f}, //ZOID - used with autocam
|
|
|
|
{"snap", SV_NoSnap_f},
|
|
|
|
{NULL, NULL}
|
|
};
|
|
|
|
/*
|
|
==================
|
|
SV_ExecuteUserCommand
|
|
==================
|
|
*/
|
|
void SV_ExecuteUserCommand (char *s)
|
|
{
|
|
ucmd_t *u;
|
|
|
|
Cmd_TokenizeString (s);
|
|
sv_player = host_client->edict;
|
|
|
|
SV_BeginRedirect (RD_CLIENT);
|
|
|
|
for (u=ucmds ; u->name ; u++)
|
|
if (!strcmp (Cmd_Argv(0), u->name) )
|
|
{
|
|
u->func ();
|
|
break;
|
|
}
|
|
|
|
if (!u->name)
|
|
Con_Printf ("Bad user command: %s\n", Cmd_Argv(0));
|
|
|
|
SV_EndRedirect ();
|
|
}
|
|
|
|
/*
|
|
===========================================================================
|
|
|
|
USER CMD EXECUTION
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
V_CalcRoll
|
|
|
|
Used by view and sv_user
|
|
===============
|
|
*/
|
|
float V_CalcRoll (vec3_t angles, vec3_t velocity)
|
|
{
|
|
vec3_t forward, right, up;
|
|
float sign;
|
|
float side;
|
|
float value;
|
|
|
|
AngleVectors (angles, forward, right, up);
|
|
side = DotProduct (velocity, right);
|
|
sign = side < 0 ? -1 : 1;
|
|
side = fabs(side);
|
|
|
|
value = cl_rollangle.value;
|
|
|
|
if (side < cl_rollspeed.value)
|
|
side = side * value / cl_rollspeed.value;
|
|
else
|
|
side = value;
|
|
|
|
return side*sign;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
vec3_t pmove_mins, pmove_maxs;
|
|
|
|
/*
|
|
====================
|
|
AddLinksToPmove
|
|
|
|
====================
|
|
*/
|
|
void AddLinksToPmove ( areanode_t *node )
|
|
{
|
|
link_t *l, *next;
|
|
edict_t *check;
|
|
int pl;
|
|
int i;
|
|
physent_t *pe;
|
|
|
|
pl = EDICT_TO_PROG(sv_player);
|
|
|
|
// touch linked edicts
|
|
for (l = node->solid_edicts.next ; l != &node->solid_edicts ; l = next)
|
|
{
|
|
next = l->next;
|
|
check = EDICT_FROM_AREA(l);
|
|
|
|
if (check->v.owner == pl)
|
|
continue; // player's own missile
|
|
if (check->v.solid == SOLID_BSP
|
|
|| check->v.solid == SOLID_BBOX
|
|
|| check->v.solid == SOLID_SLIDEBOX)
|
|
{
|
|
if (check == sv_player)
|
|
continue;
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
if (check->v.absmin[i] > pmove_maxs[i]
|
|
|| check->v.absmax[i] < pmove_mins[i])
|
|
break;
|
|
if (i != 3)
|
|
continue;
|
|
if (pmove.numphysent == MAX_PHYSENTS)
|
|
return;
|
|
pe = &pmove.physents[pmove.numphysent];
|
|
pmove.numphysent++;
|
|
|
|
VectorCopy (check->v.origin, pe->origin);
|
|
pe->info = NUM_FOR_EDICT(check);
|
|
if (check->v.solid == SOLID_BSP)
|
|
pe->model = sv.models[(int)(check->v.modelindex)];
|
|
else
|
|
{
|
|
pe->model = NULL;
|
|
VectorCopy (check->v.mins, pe->mins);
|
|
VectorCopy (check->v.maxs, pe->maxs);
|
|
}
|
|
}
|
|
}
|
|
|
|
// recurse down both sides
|
|
if (node->axis == -1)
|
|
return;
|
|
|
|
if ( pmove_maxs[node->axis] > node->dist )
|
|
AddLinksToPmove ( node->children[0] );
|
|
if ( pmove_mins[node->axis] < node->dist )
|
|
AddLinksToPmove ( node->children[1] );
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
AddAllEntsToPmove
|
|
|
|
For debugging
|
|
================
|
|
*/
|
|
void AddAllEntsToPmove (void)
|
|
{
|
|
int e;
|
|
edict_t *check;
|
|
int i;
|
|
physent_t *pe;
|
|
int pl;
|
|
|
|
pl = EDICT_TO_PROG(sv_player);
|
|
check = NEXT_EDICT(sv.edicts);
|
|
for (e=1 ; e<sv.num_edicts ; e++, check = NEXT_EDICT(check))
|
|
{
|
|
if (check->free)
|
|
continue;
|
|
if (check->v.owner == pl)
|
|
continue;
|
|
if (check->v.solid == SOLID_BSP
|
|
|| check->v.solid == SOLID_BBOX
|
|
|| check->v.solid == SOLID_SLIDEBOX)
|
|
{
|
|
if (check == sv_player)
|
|
continue;
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
if (check->v.absmin[i] > pmove_maxs[i]
|
|
|| check->v.absmax[i] < pmove_mins[i])
|
|
break;
|
|
if (i != 3)
|
|
continue;
|
|
pe = &pmove.physents[pmove.numphysent];
|
|
|
|
VectorCopy (check->v.origin, pe->origin);
|
|
pmove.physents[pmove.numphysent].info = e;
|
|
if (check->v.solid == SOLID_BSP)
|
|
pe->model = sv.models[(int)(check->v.modelindex)];
|
|
else
|
|
{
|
|
pe->model = NULL;
|
|
VectorCopy (check->v.mins, pe->mins);
|
|
VectorCopy (check->v.maxs, pe->maxs);
|
|
}
|
|
|
|
if (++pmove.numphysent == MAX_PHYSENTS)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===========
|
|
SV_PreRunCmd
|
|
===========
|
|
Done before running a player command. Clears the touch array
|
|
*/
|
|
byte playertouch[(MAX_EDICTS+7)/8];
|
|
|
|
void SV_PreRunCmd(void)
|
|
{
|
|
memset(playertouch, 0, sizeof(playertouch));
|
|
}
|
|
|
|
/* how many packets to average the ideal 'msec' value over */
|
|
#define MSECSIZE 100
|
|
|
|
/* maximum msec offset we will allow before penalizing the player */
|
|
#define POOLMAX 150
|
|
|
|
/*
|
|
===========
|
|
SV_RunCmd
|
|
===========
|
|
*/
|
|
void SV_RunCmd (usercmd_t *ucmd, qboolean inside)
|
|
{
|
|
edict_t *ent;
|
|
int i, n, oldmsec, idealmsec;
|
|
double tmp_time;
|
|
|
|
// To prevent a infinite loop
|
|
if (!inside) {
|
|
oldmsec = ucmd->msec;
|
|
// Calculate the ideal msec.
|
|
tmp_time = realtime - host_client->frame_time;
|
|
idealmsec = tmp_time * 1000;
|
|
|
|
if (loss)
|
|
idealmsec /= (loss + 1);
|
|
|
|
// Cap it at a max of 250 msec though..
|
|
idealmsec = min(idealmsec, 250);
|
|
idealmsec = max(idealmsec, 10);
|
|
|
|
host_client->msec_total -= host_client->msecs[host_client->msec_head];
|
|
host_client->msec_total += idealmsec;
|
|
|
|
host_client->msecs[host_client->msec_head] = idealmsec;
|
|
host_client->msec_head = (host_client->msec_head + 1) % MSECSIZE;
|
|
host_client->msec_count = min(MSECSIZE, host_client->msec_count + 1);
|
|
|
|
host_client->msec_pool += idealmsec - oldmsec;
|
|
|
|
#if 0
|
|
if (abs(host_client->msec_pool) > POOLMAX)
|
|
{
|
|
printf("tmp_t: %f, realt: %f, frame_t: %f, loss: %d, total: %d\n",
|
|
tmp_time, realtime, host_client->frame_time, loss,
|
|
host_client->msec_total);
|
|
/*
|
|
printf("%3d %3d %3d %3d %3d ",
|
|
host_client->msecs[0],
|
|
host_client->msecs[1],
|
|
host_client->msecs[2],
|
|
host_client->msecs[3],
|
|
host_client->msecs[4]);
|
|
*/
|
|
if (abs(host_client->msec_pool) > POOLMAX)
|
|
printf("*** ");
|
|
else
|
|
printf(" ");
|
|
|
|
printf("oldmsec: %d, msec: %d, pool: %d\n", oldmsec,
|
|
idealmsec, host_client->msec_pool);
|
|
}
|
|
#endif
|
|
|
|
if (abs(host_client->msec_pool) > POOLMAX)
|
|
{
|
|
/* if our pool exceeds POOLMAX msec, empty it into msec. This
|
|
should cause the client to 'jitter' back a bit as his
|
|
position is reset. */
|
|
ucmd->msec = oldmsec - host_client->msec_pool;
|
|
host_client->msec_pool = 0;
|
|
} else
|
|
{
|
|
/* trust the client - if he's cheating, his pool will overflow
|
|
anyway within a couple of packets. */
|
|
ucmd->msec = oldmsec;
|
|
}
|
|
|
|
host_client->frame_time = realtime;
|
|
}
|
|
|
|
cmd = *ucmd;
|
|
|
|
// chop up very long commands
|
|
if (cmd.msec > 50) {
|
|
oldmsec = ucmd->msec;
|
|
cmd.msec = oldmsec/2;
|
|
SV_RunCmd (&cmd, 1);
|
|
cmd.msec = oldmsec/2;
|
|
cmd.impulse = 0;
|
|
SV_RunCmd (&cmd, 1);
|
|
return;
|
|
}
|
|
|
|
if (!sv_player->v.fixangle)
|
|
VectorCopy (ucmd->angles, sv_player->v.v_angle);
|
|
|
|
sv_player->v.button0 = ucmd->buttons & 1;
|
|
sv_player->v.button2 = (ucmd->buttons & 2)>>1;
|
|
if (ucmd->impulse)
|
|
sv_player->v.impulse = ucmd->impulse;
|
|
|
|
//
|
|
// angles
|
|
// show 1/3 the pitch angle and all the roll angle
|
|
if (sv_player->v.health > 0)
|
|
{
|
|
if (!sv_player->v.fixangle)
|
|
{
|
|
sv_player->v.angles[PITCH] = -sv_player->v.v_angle[PITCH]/3;
|
|
sv_player->v.angles[YAW] = sv_player->v.v_angle[YAW];
|
|
}
|
|
sv_player->v.angles[ROLL] =
|
|
V_CalcRoll (sv_player->v.angles, sv_player->v.velocity)*4;
|
|
}
|
|
|
|
host_frametime = ucmd->msec * 0.001;
|
|
if (host_frametime > 0.1)
|
|
host_frametime = 0.1;
|
|
|
|
if (!host_client->spectator)
|
|
{
|
|
pr_global_struct->frametime = host_frametime;
|
|
|
|
pr_global_struct->time = sv.time;
|
|
pr_global_struct->self = EDICT_TO_PROG(sv_player);
|
|
PR_ExecuteProgram (pr_global_struct->PlayerPreThink);
|
|
|
|
SV_RunThink (sv_player);
|
|
}
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
pmove.origin[i] = sv_player->v.origin[i] + (sv_player->v.mins[i] - player_mins[i]);
|
|
VectorCopy (sv_player->v.velocity, pmove.velocity);
|
|
VectorCopy (sv_player->v.v_angle, pmove.angles);
|
|
|
|
pmove.spectator = host_client->spectator;
|
|
pmove.waterjumptime = sv_player->v.teleport_time;
|
|
pmove.numphysent = 1;
|
|
pmove.physents[0].model = sv.worldmodel;
|
|
pmove.cmd = *ucmd;
|
|
pmove.dead = sv_player->v.health <= 0;
|
|
pmove.oldbuttons = host_client->oldbuttons;
|
|
|
|
movevars.entgravity = host_client->entgravity;
|
|
movevars.maxspeed = host_client->maxspeed;
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
pmove_mins[i] = pmove.origin[i] - 256;
|
|
pmove_maxs[i] = pmove.origin[i] + 256;
|
|
}
|
|
#if 1
|
|
AddLinksToPmove ( sv_areanodes );
|
|
#else
|
|
AddAllEntsToPmove ();
|
|
#endif
|
|
|
|
#if 0
|
|
{
|
|
int before, after;
|
|
|
|
before = PM_TestPlayerPosition (pmove.origin);
|
|
PlayerMove ();
|
|
after = PM_TestPlayerPosition (pmove.origin);
|
|
|
|
if (sv_player->v.health > 0 && before && !after )
|
|
Con_Printf ("player %s got stuck in playermove!!!!\n", host_client->name);
|
|
}
|
|
#else
|
|
PlayerMove ();
|
|
#endif
|
|
|
|
host_client->oldbuttons = pmove.oldbuttons;
|
|
sv_player->v.teleport_time = pmove.waterjumptime;
|
|
sv_player->v.waterlevel = waterlevel;
|
|
sv_player->v.watertype = watertype;
|
|
if (onground != -1)
|
|
{
|
|
sv_player->v.flags = (int)sv_player->v.flags | FL_ONGROUND;
|
|
sv_player->v.groundentity = EDICT_TO_PROG(EDICT_NUM(pmove.physents[onground].info));
|
|
}
|
|
else
|
|
sv_player->v.flags = (int)sv_player->v.flags & ~FL_ONGROUND;
|
|
for (i=0 ; i<3 ; i++)
|
|
sv_player->v.origin[i] = pmove.origin[i] - (sv_player->v.mins[i] - player_mins[i]);
|
|
|
|
#if 0
|
|
// truncate velocity the same way the net protocol will
|
|
for (i=0 ; i<3 ; i++)
|
|
sv_player->v.velocity[i] = (int)pmove.velocity[i];
|
|
#else
|
|
VectorCopy (pmove.velocity, sv_player->v.velocity);
|
|
#endif
|
|
|
|
VectorCopy (pmove.angles, sv_player->v.v_angle);
|
|
|
|
if (!host_client->spectator)
|
|
{
|
|
// link into place and touch triggers
|
|
SV_LinkEdict (sv_player, true);
|
|
|
|
// touch other objects
|
|
for (i=0 ; i<pmove.numtouch ; i++)
|
|
{
|
|
n = pmove.physents[pmove.touchindex[i]].info;
|
|
ent = EDICT_NUM(n);
|
|
if (!ent->v.touch || (playertouch[n/8]&(1<<(n%8))))
|
|
continue;
|
|
pr_global_struct->self = EDICT_TO_PROG(ent);
|
|
pr_global_struct->other = EDICT_TO_PROG(sv_player);
|
|
PR_ExecuteProgram (ent->v.touch);
|
|
playertouch[n/8] |= 1 << (n%8);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===========
|
|
SV_PostRunCmd
|
|
===========
|
|
Done after running a player command.
|
|
*/
|
|
void SV_PostRunCmd(void)
|
|
{
|
|
// run post-think
|
|
|
|
if (!host_client->spectator) {
|
|
pr_global_struct->time = sv.time;
|
|
pr_global_struct->self = EDICT_TO_PROG(sv_player);
|
|
PR_ExecuteProgram (pr_global_struct->PlayerPostThink);
|
|
SV_RunNewmis ();
|
|
} else if (SpectatorThink) {
|
|
pr_global_struct->time = sv.time;
|
|
pr_global_struct->self = EDICT_TO_PROG(sv_player);
|
|
PR_ExecuteProgram (SpectatorThink);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
SV_ExecuteClientMessage
|
|
|
|
The current net_message is parsed for the given client
|
|
===================
|
|
*/
|
|
void SV_ExecuteClientMessage (client_t *cl)
|
|
{
|
|
int c;
|
|
char *s;
|
|
usercmd_t oldest, oldcmd, newcmd;
|
|
client_frame_t *frame;
|
|
vec3_t o;
|
|
qboolean move_issued = false; //only allow one move command
|
|
int checksumIndex;
|
|
byte checksum, calculatedChecksum;
|
|
int seq_hash;
|
|
|
|
// calc ping time
|
|
frame = &cl->frames[cl->netchan.incoming_acknowledged & UPDATE_MASK];
|
|
frame->ping_time = realtime - frame->senttime;
|
|
|
|
// make sure the reply sequence number matches the incoming
|
|
// sequence number
|
|
if (cl->netchan.incoming_sequence >= cl->netchan.outgoing_sequence)
|
|
cl->netchan.outgoing_sequence = cl->netchan.incoming_sequence;
|
|
else
|
|
cl->send_message = false; // don't reply, sequences have slipped
|
|
|
|
// save time for ping calculations
|
|
cl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].senttime = realtime;
|
|
cl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].ping_time = -1;
|
|
|
|
host_client = cl;
|
|
sv_player = host_client->edict;
|
|
|
|
// seq_hash = (cl->netchan.incoming_sequence & 0xffff) ; // ^ QW_CHECK_HASH;
|
|
seq_hash = cl->netchan.incoming_sequence;
|
|
|
|
// mark time so clients will know how much to predict
|
|
// other players
|
|
cl->localtime = sv.time;
|
|
cl->delta_sequence = -1; // no delta unless requested
|
|
while (1)
|
|
{
|
|
if (msg_badread)
|
|
{
|
|
Con_Printf ("SV_ReadClientMessage: badread\n");
|
|
SV_DropClient (cl);
|
|
return;
|
|
}
|
|
|
|
c = MSG_ReadByte ();
|
|
if (c == -1)
|
|
break;
|
|
|
|
switch (c)
|
|
{
|
|
default:
|
|
Con_Printf ("SV_ReadClientMessage: unknown command char\n");
|
|
SV_DropClient (cl);
|
|
return;
|
|
|
|
case clc_nop:
|
|
break;
|
|
|
|
case clc_delta:
|
|
cl->delta_sequence = MSG_ReadByte ();
|
|
break;
|
|
|
|
case clc_move:
|
|
if (move_issued)
|
|
return; // someone is trying to cheat...
|
|
|
|
move_issued = true;
|
|
|
|
checksumIndex = MSG_GetReadCount();
|
|
checksum = (byte)MSG_ReadByte ();
|
|
|
|
// read loss percentage
|
|
cl->lossage = MSG_ReadByte();
|
|
|
|
MSG_ReadDeltaUsercmd (&nullcmd, &oldest);
|
|
MSG_ReadDeltaUsercmd (&oldest, &oldcmd);
|
|
MSG_ReadDeltaUsercmd (&oldcmd, &newcmd);
|
|
|
|
if ( cl->state != cs_spawned )
|
|
break;
|
|
|
|
// if the checksum fails, ignore the rest of the packet
|
|
calculatedChecksum = COM_BlockSequenceCRCByte(
|
|
net_message.data + checksumIndex + 1,
|
|
MSG_GetReadCount() - checksumIndex - 1,
|
|
seq_hash);
|
|
|
|
if (calculatedChecksum != checksum)
|
|
{
|
|
Con_DPrintf ("Failed command checksum for %s(%d) (%d != %d)\n",
|
|
cl->name, cl->netchan.incoming_sequence, checksum, calculatedChecksum);
|
|
return;
|
|
}
|
|
|
|
if (!sv.paused) {
|
|
SV_PreRunCmd();
|
|
|
|
loss = net_drop;
|
|
|
|
if (net_drop < 20) {
|
|
while (net_drop > 2) {
|
|
SV_RunCmd (&cl->lastcmd, 1);
|
|
net_drop--;
|
|
}
|
|
if (net_drop > 1)
|
|
SV_RunCmd (&oldest, 1);
|
|
if (net_drop > 0)
|
|
SV_RunCmd (&oldcmd, 1);
|
|
}
|
|
|
|
SV_RunCmd (&newcmd, 0);
|
|
|
|
SV_PostRunCmd();
|
|
}
|
|
|
|
cl->lastcmd = newcmd;
|
|
cl->lastcmd.buttons = 0; // avoid multiple fires on lag
|
|
break;
|
|
|
|
|
|
case clc_stringcmd:
|
|
s = MSG_ReadString ();
|
|
SV_ExecuteUserCommand (s);
|
|
break;
|
|
|
|
case clc_tmove:
|
|
o[0] = MSG_ReadCoord();
|
|
o[1] = MSG_ReadCoord();
|
|
o[2] = MSG_ReadCoord();
|
|
// only allowed by spectators
|
|
if (host_client->spectator) {
|
|
VectorCopy(o, sv_player->v.origin);
|
|
SV_LinkEdict(sv_player, false);
|
|
}
|
|
break;
|
|
|
|
case clc_upload:
|
|
SV_NextUpload();
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
SV_UserInit
|
|
==============
|
|
*/
|
|
void SV_UserInit (void)
|
|
{
|
|
Cvar_RegisterVariable (&cl_rollspeed);
|
|
Cvar_RegisterVariable (&cl_rollangle);
|
|
Cvar_RegisterVariable (&sv_spectalk);
|
|
Cvar_RegisterVariable (&sv_mapcheck);
|
|
}
|
|
|
|
|