diff options
Diffstat (limited to 'QW/server/sv_user.c')
| -rw-r--r-- | QW/server/sv_user.c | 1702 |
1 files changed, 1702 insertions, 0 deletions
diff --git a/QW/server/sv_user.c b/QW/server/sv_user.c new file mode 100644 index 0000000..a8e5570 --- /dev/null +++ b/QW/server/sv_user.c @@ -0,0 +1,1702 @@ +/* +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 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; + +/* +============================================================ + +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); + vsprintf (send+5, fmt, argptr); + va_end (argptr); + + NET_SendPacket (strlen(send)+1, send, where); +} + +/* +================== +SV_NextUpload +================== +*/ +void SV_NextUpload (void) +{ + byte buffer[1024]; + int r; + int percent; + int size; + client_t *client; + + 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)) + sprintf (text, "[SPEC] %s: ", host_client->name); + else if (team) + sprintf (text, "(%s): ", host_client->name); + else { + sprintf (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, SV_CalcPing(client)); + 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) +{ + int i; + client_t *cl; + 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) + sprintf (st, "%s paused the game\n", host_client->name); + else + sprintf (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)); +} + +/* +=========== +SV_RunCmd +=========== +*/ +void SV_RunCmd (usercmd_t *ucmd) +{ + edict_t *ent; + int i, n; + int oldmsec; + + cmd = *ucmd; + + // chop up very long commands + if (cmd.msec > 50) + { + oldmsec = ucmd->msec; + cmd.msec = oldmsec/2; + SV_RunCmd (&cmd); + cmd.msec = oldmsec/2; + cmd.impulse = 0; + SV_RunCmd (&cmd); + 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(); + + if (net_drop < 20) + { + while (net_drop > 2) + { + SV_RunCmd (&cl->lastcmd); + net_drop--; + } + if (net_drop > 1) + SV_RunCmd (&oldest); + if (net_drop > 0) + SV_RunCmd (&oldcmd); + } + SV_RunCmd (&newcmd); + + 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); +} + + |
