1
0
Fork 0
forked from fte/fteqw
fteqw/engine/server/sv_user.c
Spoike 9ae7e2621d SOFTWARE RENDERING IS BROKEN: DO NOT USE ASM VERSION.
Lots of changes.
CSQC should be functional, but is still tied to debug builds. It WILL have some bugs still, hopefully I'll be able to clean them up better if people test it a bit.
Precompiled headers are working properly now. Compile times are now much quicker in msvc. This takes most of the files this commit.
Restructured how client commands work. They're buffered outside the network message, some multithreaded code is in. It needs a bit of testing before it's active.


git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@885 fc73d0e0-1445-4013-8a0c-d673dee63da5
2005-02-28 07:16:19 +00:00

5052 lines
123 KiB
C

/*
Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the 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"
#ifndef CLIENTONLY
#include <ctype.h>
#define Q2EDICT_NUM(i) (q2edict_t*)((char *)ge->edicts+i*ge->edict_size)
#define Q2NUM_FOR_EDICT(ent) (((char *)ent - (char *)ge->edicts) / ge->edict_size)
hull_t *SV_HullForEntity (edict_t *ent, int hullnum, vec3_t mins, vec3_t maxs, vec3_t offset);
edict_t *sv_player;
usercmd_t cmd;
#ifdef SERVERONLY
cvar_t cl_rollspeed = {"cl_rollspeed", "200"};
cvar_t cl_rollangle = {"cl_rollangle", "2.0"};
#else
extern cvar_t cl_rollspeed;
extern cvar_t cl_rollangle;
#endif
cvar_t sv_spectalk = {"sv_spectalk", "1"};
cvar_t sv_mapcheck = {"sv_mapcheck", "1"};
cvar_t sv_cheatpc = {"sv_cheatpc", "125"};
cvar_t sv_cheatspeedchecktime = {"sv_cheatspeedchecktime", "30"};
cvar_t sv_playermodelchecks = {"sv_playermodelchecks", "1"};
cvar_t sv_cmdlikercon = {"sv_cmdlikercon", "0"};
cvar_t sv_nomsec = {"sv_nomsec", "0"};
cvar_t sv_edgefriction = {"sv_edgefriction", "2"};
cvar_t sv_brokenmovetypes = {"sv_brokenmovetypes", "0"};
cvar_t sv_chatfilter = {"sv_chatfilter", "0"};
cvar_t votelevel = {"votelevel", "0"};
cvar_t voteminimum = {"voteminimum", "4"};
cvar_t votepercent = {"votepercent", "-1"};
cvar_t votetime = {"votetime", "10"};
cvar_t pr_allowbutton1 = {"pr_allowbutton1", "1", NULL, CVAR_LATCH};
extern cvar_t pm_bunnyspeedcap;
extern cvar_t pm_ktjump;
extern cvar_t pm_slidefix;
extern cvar_t pm_airstep;
extern cvar_t pm_walljump;
char sv_votinggroup[] = "server voting";
extern char cvargroup_serverpermissions[];
extern char cvargroup_serverinfo[];
extern char cvargroup_serverphysics[];
extern char cvargroup_servercontrol[];
extern vec3_t player_mins, player_maxs;
extern int fp_messages, fp_persecond, fp_secondsdead;
extern char fp_msg[];
extern cvar_t pausable;
void SV_PreRunCmd(void);
void SV_RunCmd (usercmd_t *ucmd, qboolean recurse);
void SV_PostRunCmd(void);
/*
============================================================
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;
int splitnum;
client_t *split;
if (host_client->state == cs_spawned)
return;
#ifdef NQPROT
host_client->nqprot = false;
#endif
/* splitt delay
host_client->state = cs_connected;
host_client->connection_started = realtime;
#ifdef SVRANKING
host_client->stats_started = realtime;
#endif*/
// 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_TPrintf(STL_BACKBUFSET, host_client->name, host_client->netchan.message.cursize);
host_client->num_backbuf = 0;
SZ_Clear(&host_client->netchan.message);
}
if (sizeofcoord > 2 && !(host_client->fteprotocolextensions & PEXT_FLOATCOORDS))
{
SV_ClientPrintf(host_client, 2, "\n\n\n\nSorry, but your client does not appear to support FTE's bigcoords\nFTE users will need to set cl_nopext to 0 and then reconnect, or to upgrade\n");
Con_Printf("%s does not support bigcoords\n", host_client->name);
return;
}
// send the serverdata
MSG_WriteByte (&host_client->netchan.message, host_client->isq2client?svcq2_serverdata:svc_serverdata);
#ifdef PROTOCOL_VERSION_FTE
if (host_client->fteprotocolextensions)//let the client know
{
MSG_WriteLong (&host_client->netchan.message, PROTOCOL_VERSION_FTE);
if (sizeofcoord == 2) //we're not using float orgs on this level.
MSG_WriteLong (&host_client->netchan.message, host_client->fteprotocolextensions&~PEXT_FLOATCOORDS);
else
MSG_WriteLong (&host_client->netchan.message, host_client->fteprotocolextensions);
}
#endif
MSG_WriteLong (&host_client->netchan.message, host_client->isq2client?host_client->isq2client:PROTOCOL_VERSION);
MSG_WriteLong (&host_client->netchan.message, svs.spawncount);
if (host_client->isq2client)
MSG_WriteByte (&host_client->netchan.message, 0);
MSG_WriteString (&host_client->netchan.message, gamedir);
splitnum = 0;
for (split = host_client; split; split = split->controlled)
{
#ifdef Q2SERVER
if (!svprogfuncs)
playernum = Q2NUM_FOR_EDICT(split->q2edict)-1;
else
#endif
playernum = NUM_FOR_EDICT(svprogfuncs, split->edict)-1;
if (sv.demostate)
{
playernum = (MAX_CLIENTS-1-splitnum)|128;
}
else if (split->spectator)
playernum |= 128;
if (sv.state == ss_cinematic)
playernum = -1;
if (host_client->isq2client)
MSG_WriteShort (&host_client->netchan.message, playernum);
else
MSG_WriteByte (&host_client->netchan.message, playernum);
split->state = cs_connected;
split->connection_started = realtime;
#ifdef SVRANKING
split->stats_started = realtime;
#endif
splitnum++;
}
if (host_client->fteprotocolextensions & PEXT_SPLITSCREEN)
MSG_WriteByte (&host_client->netchan.message, 128);
// send full levelname
if (sv.demostatevalid)
MSG_WriteString (&host_client->netchan.message, sv.demfullmapname);
else
MSG_WriteString (&host_client->netchan.message, sv.mapname);
//
// game server
//
#ifdef Q2SERVER
if (host_client->isq2client)
{
if (sv.state != ss_cinematic)
{
// host_client->q2edict = Q2NUM_FOR_EDICT(split->q2edict)-1;
memset (&host_client->lastcmd, 0, sizeof(host_client->lastcmd));
// begin fetching configstrings
MSG_WriteByte (&host_client->netchan.message, svcq2_stufftext);
MSG_WriteString (&host_client->netchan.message, va("cmd configstrings %i 0\n",svs.spawncount) );
}
return;
}
#endif
// 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 server info string
MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
if (sv.demostatevalid)
MSG_WriteString (&host_client->netchan.message, va("fullserverinfo \"%s\"\n", sv.demoinfo));
else
MSG_WriteString (&host_client->netchan.message, va("fullserverinfo \"%s\"\n", svs.info) );
// send music
MSG_WriteByte (&host_client->netchan.message, svc_cdtrack);
if (svprogfuncs)
MSG_WriteByte (&host_client->netchan.message, sv.edicts->v.sounds);
else
MSG_WriteByte (&host_client->netchan.message, 0);
}
#define NQ_PROTOCOL_VERSION 15
#define GAME_DEATHMATCH 0
#define GAME_COOP 1
void SVNQ_New_f (void)
{
extern cvar_t coop;
char message[2048];
int i;
#ifdef NQPROT
host_client->nqprot = true;
#endif
MSG_WriteByte (&host_client->netchan.message, svc_print);
#ifdef DISTRIBUTION
sprintf (message, "%c\n" DISTRIBUTION " QuakeWorld version %4.2f server\n", 2, VERSION);
#else
sprintf (message, "%c\nQUAKEWORLD VERSION %4.2f SERVER\n", 2, VERSION);
#endif
MSG_WriteString (&host_client->netchan.message,message);
MSG_WriteByte (&host_client->netchan.message, svc_serverdata);
MSG_WriteLong (&host_client->netchan.message, NQ_PROTOCOL_VERSION);
MSG_WriteByte (&host_client->netchan.message, 16);
if (!coop.value && deathmatch.value)
MSG_WriteByte (&host_client->netchan.message, GAME_DEATHMATCH);
else
MSG_WriteByte (&host_client->netchan.message, GAME_COOP);
strcpy (message, sv.mapname);
MSG_WriteString (&host_client->netchan.message,message);
for (i = 1; *sv.model_precache[i] ; i++)
MSG_WriteString (&host_client->netchan.message, sv.model_precache[i]);
MSG_WriteByte (&host_client->netchan.message, 0);
for (i = 1; *sv.sound_precache[i] ; i++)
MSG_WriteString (&host_client->netchan.message, sv.sound_precache[i]);
MSG_WriteByte (&host_client->netchan.message, 0);
// send music
MSG_WriteByte (&host_client->netchan.message, svc_cdtrack);
MSG_WriteByte (&host_client->netchan.message, sv.edicts->v.sounds);
MSG_WriteByte (&host_client->netchan.message, sv.edicts->v.sounds);
// set view
MSG_WriteByte (&host_client->netchan.message, svc_setview);
MSG_WriteShort (&host_client->netchan.message, NUM_FOR_EDICT(svprogfuncs, host_client->edict));
MSG_WriteByte (&host_client->netchan.message, svc_signonnum);
MSG_WriteByte (&host_client->netchan.message, 1);
MSG_WriteByte (&host_client->netchan.message, svc_setpause);
MSG_WriteByte (&host_client->netchan.message, sv.paused);
// host_client->sendsignon = true;
// host_client->spawned = false; // need prespawn, spawn, etc
}
#ifdef Q2SERVER
void SVQ2_ConfigStrings_f (void)
{
extern int map_checksum;
int start;
Con_DPrintf ("Configstrings() from %s\n", host_client->name);
if (host_client->state != cs_connected)
{
Con_DPrintf ("configstrings not valid -- already spawned\n");
return;
}
// handle the case of a level changing while a client was connecting
if ( atoi(Cmd_Argv(1)) != svs.spawncount )
{
Con_DPrintf ("SV_Configstrings_f from different level\n");
SV_New_f ();
return;
}
start = atoi(Cmd_Argv(2));
if (start < 0)
{
Con_Printf ("SV_Configstrings_f: %s tried crashing us\n", host_client->name);
host_client->drop = true;
return;
}
// write a packet full of data
while ( host_client->netchan.message.cursize < MAX_QWMSGLEN/2
&& start < Q2MAX_CONFIGSTRINGS)
{
//choose range to grab from.
if (start < Q2CS_CDTRACK)
{
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
MSG_WriteShort (&host_client->netchan.message, start);
MSG_WriteString (&host_client->netchan.message, sv.name);
/* if (svprogfuncs)
MSG_WriteString (&host_client->netchan.message, va("%s", PR_GetString(svprogfuncs, sv.edicts->v.message)));
else
MSG_WriteString (&host_client->netchan.message, "LEVEL NAME");
*/ }
else if (start < Q2CS_SKY)
{
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
MSG_WriteShort (&host_client->netchan.message, start);
if (svprogfuncs)
MSG_WriteString (&host_client->netchan.message, va("%i", sv.edicts->v.sounds));
else
MSG_WriteString (&host_client->netchan.message, "0");
}
else if (start < Q2CS_SKYAXIS)
{
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
MSG_WriteShort (&host_client->netchan.message, start);
MSG_WriteString (&host_client->netchan.message, "unit1_");
}
else if (start < Q2CS_SKYROTATE)
{
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
MSG_WriteShort (&host_client->netchan.message, start);
MSG_WriteString (&host_client->netchan.message, "0");
}
else if (start < Q2CS_STATUSBAR)
{
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
MSG_WriteShort (&host_client->netchan.message, start);
MSG_WriteString (&host_client->netchan.message, "0");
}
else if (start < Q2CS_AIRACCEL)
{//show status bar
if (start == Q2CS_STATUSBAR)
{
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
MSG_WriteShort (&host_client->netchan.message, start);
MSG_WriteString (&host_client->netchan.message, sv.statusbar);
}
}
else if (start < Q2CS_MAXCLIENTS)
{
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
MSG_WriteShort (&host_client->netchan.message, start);
MSG_WriteString (&host_client->netchan.message, va("%i", (int)1));
}
else if (start < Q2CS_MAPCHECKSUM)
{
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
MSG_WriteShort (&host_client->netchan.message, start);
MSG_WriteString (&host_client->netchan.message, va("%i", (int)32));
}
else if (start < Q2CS_MODELS)
{
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
MSG_WriteShort (&host_client->netchan.message, start);
MSG_WriteString (&host_client->netchan.message, va("%i", map_checksum));
}
else if (start < Q2CS_SOUNDS)
{
if (*sv.model_precache[start-Q2CS_MODELS])
{
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
MSG_WriteShort (&host_client->netchan.message, start);
MSG_WriteString (&host_client->netchan.message, sv.model_precache[start-Q2CS_MODELS]);
}
}
else if (start < Q2CS_IMAGES)
{
if (*sv.sound_precache[start-Q2CS_SOUNDS])
{
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
MSG_WriteShort (&host_client->netchan.message, start);
MSG_WriteString (&host_client->netchan.message, sv.sound_precache[start-Q2CS_SOUNDS]);
}
}
else if (start < Q2CS_LIGHTS)
{
if (*sv.image_precache[start-Q2CS_IMAGES])
{
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
MSG_WriteShort (&host_client->netchan.message, start);
MSG_WriteString (&host_client->netchan.message, sv.image_precache[start-Q2CS_IMAGES]);
}
}
else if (start < Q2CS_ITEMS)
{
if (start-Q2CS_LIGHTS < MAX_LIGHTSTYLES && sv.lightstyles[start-Q2CS_LIGHTS])
{
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
MSG_WriteShort (&host_client->netchan.message, start);
MSG_WriteString (&host_client->netchan.message, sv.lightstyles[start-Q2CS_LIGHTS]);
}
}
else if (start < Q2CS_PLAYERSKINS)
{
// MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
// MSG_WriteShort (&host_client->netchan.message, start);
// MSG_WriteString (&host_client->netchan.message, sv.configstrings[start-Q2CS_ITEMS]);
}
else if (start < Q2CS_GENERAL)
{
// MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
// MSG_WriteShort (&host_client->netchan.message, start);
// MSG_WriteString (&host_client->netchan.message, sv.configstrings[start]);
}
else
{
// MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
// MSG_WriteShort (&host_client->netchan.message, start);
// MSG_WriteString (&host_client->netchan.message, sv.configstrings[start]);
}
start++;
}
// send next command
if (start == Q2MAX_CONFIGSTRINGS)
{
MSG_WriteByte (&host_client->netchan.message, svcq2_stufftext);
MSG_WriteString (&host_client->netchan.message, va("cmd baselines %i 0\n",svs.spawncount) );
}
else
{
MSG_WriteByte (&host_client->netchan.message, svcq2_stufftext);
MSG_WriteString (&host_client->netchan.message, va("cmd configstrings %i %i\n",svs.spawncount, start) );
}
}
#endif
#ifdef Q2SERVER
void SVQ2_BaseLines_f (void)
{
int start;
q2entity_state_t nullstate;
q2entity_state_t *base;
extern q2entity_state_t sv_baselines[Q2MAX_EDICTS];
Con_DPrintf ("Baselines() from %s\n", host_client->name);
if (host_client->state != cs_connected)
{
Con_Printf ("baselines not valid -- already 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_Baselines_f from different level\n");
SV_New_f ();
return;
}
start = atoi(Cmd_Argv(2));
memset (&nullstate, 0, sizeof(nullstate));
// write a packet full of data
while ( host_client->netchan.message.cursize < MAX_QWMSGLEN/2
&& start < Q2MAX_EDICTS)
{
base = &sv_baselines[start];
if (base->modelindex || base->sound || base->effects)
{
MSG_WriteByte (&host_client->netchan.message, svcq2_spawnbaseline);
MSGQ2_WriteDeltaEntity (&nullstate, base, &host_client->netchan.message, true, true);
}
start++;
}
// send next command
if (start == Q2MAX_EDICTS)
{
MSG_WriteByte (&host_client->netchan.message, svcq2_stufftext);
MSG_WriteString (&host_client->netchan.message, va("precache %i\n", svs.spawncount) );
}
else
{
MSG_WriteByte (&host_client->netchan.message, svcq2_stufftext);
MSG_WriteString (&host_client->netchan.message, va("cmd baselines %i %i\n",svs.spawncount, start) );
}
}
void SVQ2_NextServer_f (void)
{
if (!*sv.modelname && atoi(Cmd_Argv(1)) == svs.spawncount)
{
cvar_t *nsv = Cvar_FindVar("nextserver");
if (!nsv || !*nsv->string)
return;
svs.spawncount++; // make sure another doesn't sneak in
Cbuf_AddText(nsv->string, RESTRICT_LOCAL);
Cbuf_AddText("\n", RESTRICT_LOCAL);
Cvar_Set(nsv, "");
}
}
#endif
void SV_PK3List_f (void)
{
#ifndef PEXT_PK3DOWNLOADS
Con_Printf ("pk3list not valid -- It's not implemented!\n");
return;
#else
int crc;
char *name;
int i;
if (host_client->state != cs_connected)
{ //fixme: send prints instead
Con_Printf ("pk3list 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 && !sv.msgfromdemo)
{
Con_Printf ("SV_PK3List_f from different level\n");
SV_New_f ();
return;
}
i = 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 (%d), clearing", host_client->name, host_client->netchan.message.cursize);
host_client->num_backbuf = 0;
SZ_Clear(&host_client->netchan.message);
}
if (i < 0)
{
Con_Printf ("SV_PK3List_f: %s tried to crash us\n", host_client->name);
SV_DropClient(host_client);
return;
}
for (; ; i++)
{
if (host_client->netchan.message.cursize < (MAX_QWMSGLEN/2))
{ //user's buffer was too small
MSG_WriteByte(&host_client->netchan.message, svc_stufftext);
MSG_WriteString(&host_client->netchan.message, va("cmd pk3list %i %i\n", svs.spawncount, 0));
return; //and stop before we flood them
}
name = COM_GetPathInfo(i, &crc);
if (name && *name)
{
MSG_WriteByte(&host_client->netchan.message, svc_stufftext);
MSG_WriteString(&host_client->netchan.message, va("echo packfile %s\n", name));
continue; //try the next.
}
//that's all folks, move on to sound.
MSG_WriteByte(&host_client->netchan.message, svc_stufftext);
MSG_WriteString(&host_client->netchan.message, va("soundlist %i 0\n", svs.spawncount));
return;
}
#endif
}
/*
==================
SV_Soundlist_f
==================
*/
void SV_Soundlist_f (void)
{
int i;
//char **s;
unsigned 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 && !sv.msgfromdemo)
{
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 (%d), clearing", host_client->name, host_client->netchan.message.cursize);
host_client->num_backbuf = 0;
SZ_Clear(&host_client->netchan.message);
}
if (n >= MAX_SOUNDS)
{
SV_EndRedirect();
Con_Printf ("SV_Soundlist_f: %s send an invalid index\n", host_client->name);
SV_DropClient(host_client);
return;
}
MSG_WriteByte (&host_client->netchan.message, svc_soundlist);
MSG_WriteByte (&host_client->netchan.message, n);
if (sv.democausesreconnect) //read the list from somewhere else
{
for (i = 1+n;
*sv.demsound_precache[i] && host_client->netchan.message.cursize < (MAX_QWMSGLEN/2);
i++, n++)
MSG_WriteString (&host_client->netchan.message, sv.demsound_precache[i]);
if (!*sv.demsound_precache[i])
n = 0;
}
else
{
for (i = 1+n;
*sv.sound_precache[i] && host_client->netchan.message.cursize < (MAX_QWMSGLEN/2);
i++, n++)
MSG_WriteString (&host_client->netchan.message, sv.sound_precache[i]);
if (!*sv.sound_precache[i])
n = 0;
}
MSG_WriteByte (&host_client->netchan.message, 0);
// next msg
MSG_WriteByte (&host_client->netchan.message, n);
}
/*
==================
SV_Modellist_f
==================
*/
void SV_Modellist_f (void)
{
int i;
unsigned 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 && !sv.msgfromdemo)
{
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 (%d), clearing", host_client->name, host_client->netchan.message.cursize);
host_client->num_backbuf = 0;
SZ_Clear(&host_client->netchan.message);
}
if (n >= MAX_MODELS)
{
SV_EndRedirect();
Con_Printf ("SV_Modellist_f: %s send an invalid index\n", host_client->name);
SV_DropClient(host_client);
return;
}
if (n >= 255)
{
MSG_WriteByte (&host_client->netchan.message, svc_modellistshort);
MSG_WriteShort (&host_client->netchan.message, n);
}
else
{
MSG_WriteByte (&host_client->netchan.message, svc_modellist);
MSG_WriteByte (&host_client->netchan.message, n);
}
if (sv.democausesreconnect) //read the list from somewhere else
{
for (i = 1+n;
*sv.demmodel_precache[i] && ((n&255)==0||host_client->netchan.message.cursize < (MAX_QWMSGLEN/2));
i++, n++)
MSG_WriteString (&host_client->netchan.message, sv.demmodel_precache[i]);
if (!*sv.demmodel_precache[i])
n = 0;
}
else
{
for (i = 1+n;
*sv.model_precache[i] && (((n&255)==0)||(host_client->netchan.message.cursize < (MAX_QWMSGLEN/2))); //make sure we don't send a 0 next...
i++, n++)
MSG_WriteString (&host_client->netchan.message, sv.model_precache[i]);
if (!*sv.model_precache[i])
n = 0;
}
MSG_WriteByte (&host_client->netchan.message, 0);
// next msg
MSG_WriteByte (&host_client->netchan.message, n);
}
/*
==================
SV_PreSpawn_f
==================
*/
void SV_PreSpawn_f (void)
{
unsigned buf, bufs;
unsigned check;
unsigned statics;
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 && !sv.msgfromdemo)
{
Con_Printf ("SV_PreSpawn_f from different level\n");
SV_New_f ();
return;
}
if (sv.democausesreconnect)
bufs = sv.num_demosignon_buffers;
else
bufs = sv.num_signon_buffers;
statics = sv.numextrastatics;
buf = atoi(Cmd_Argv(2));
if (buf >= bufs+statics+sv.num_edicts+255)
{
SV_EndRedirect();
Con_Printf ("SV_Modellist_f: %s send an invalid index\n", host_client->name);
SV_DropClient(host_client);
return;
}
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)
if (!sv.demofile || (sv.demofile && !sv.democausesreconnect)) //demo playing causes no check. If it's the return level, check anyway to avoid that loophole.
{
SV_ClientTPrintf (host_client, PRINT_HIGH,
STL_MAPCHEAT,
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 (%d), clearing", host_client->name, host_client->netchan.message.cursize);
host_client->num_backbuf = 0;
SZ_Clear(&host_client->netchan.message);
}
if (buf >= bufs && !sv.democausesreconnect)
{
int i;
entity_state_t from;
entity_state_t *state;
edict_t *ent;
svcustomtents_t *ctent;
memset(&from, 0, sizeof(from));
while (host_client->netchan.message.cursize < (host_client->netchan.message.maxsize/2)) //static entities
{
if (buf - bufs >= sv.numextrastatics)
break;
state = &sv.extendedstatics[buf - bufs];
if (host_client->fteprotocolextensions & PEXT_SPAWNSTATIC2)
{
MSG_WriteByte(&host_client->netchan.message, svc_spawnstatic2);
SV_WriteDelta(&from, state, &host_client->netchan.message, true, host_client->fteprotocolextensions);
}
else
{
MSG_WriteByte(&host_client->netchan.message, svc_spawnstatic);
MSG_WriteByte (&host_client->netchan.message, state->modelindex&255);
MSG_WriteByte (&host_client->netchan.message, state->frame);
MSG_WriteByte (&host_client->netchan.message, (int)state->colormap);
MSG_WriteByte (&host_client->netchan.message, (int)state->skinnum);
for (i=0 ; i<3 ; i++)
{
MSG_WriteCoord(&host_client->netchan.message, state->origin[i]);
MSG_WriteAngle(&host_client->netchan.message, state->angles[i]);
}
}
buf++;
}
while (host_client->netchan.message.cursize < (host_client->netchan.message.maxsize/2)) //baselines
{
if (buf - bufs - sv.numextrastatics >= sv.num_edicts)
break;
ent = EDICT_NUM(svprogfuncs, buf - bufs - sv.numextrastatics);
if (ent->tagent)
{
MSG_WriteByte(&host_client->netchan.message, svc_setattachment);
MSG_WriteShort(&host_client->netchan.message, ent->entnum);
MSG_WriteShort(&host_client->netchan.message, ent->tagent);
MSG_WriteShort(&host_client->netchan.message, ent->tagindex);
}
state = &ent->baseline;
if (!state->number || !state->modelindex)
{ //ent doesn't have a baseline
buf++;
continue;
}
if (!ent)
{
MSG_WriteByte(&host_client->netchan.message, svc_spawnbaseline);
MSG_WriteShort (&host_client->netchan.message, buf - bufs - sv.numextrastatics);
MSG_WriteByte (&host_client->netchan.message, 0);
MSG_WriteByte (&host_client->netchan.message, 0);
MSG_WriteByte (&host_client->netchan.message, 0);
MSG_WriteByte (&host_client->netchan.message, 0);
for (i=0 ; i<3 ; i++)
{
MSG_WriteCoord(&host_client->netchan.message, 0);
MSG_WriteAngle(&host_client->netchan.message, 0);
}
}
else if (host_client->fteprotocolextensions & PEXT_SPAWNSTATIC2)
{
MSG_WriteByte(&host_client->netchan.message, svc_spawnbaseline2);
SV_WriteDelta(&from, state, &host_client->netchan.message, true, host_client->fteprotocolextensions);
}
else
{
MSG_WriteByte(&host_client->netchan.message, svc_spawnbaseline);
MSG_WriteShort (&host_client->netchan.message, buf - bufs - sv.numextrastatics);
MSG_WriteByte (&host_client->netchan.message, state->modelindex&255);
MSG_WriteByte (&host_client->netchan.message, state->frame);
MSG_WriteByte (&host_client->netchan.message, (int)state->colormap);
MSG_WriteByte (&host_client->netchan.message, (int)state->skinnum);
for (i=0 ; i<3 ; i++)
{
MSG_WriteCoord(&host_client->netchan.message, state->origin[i]);
MSG_WriteAngle(&host_client->netchan.message, state->angles[i]);
}
}
buf++;
}
while (host_client->netchan.message.cursize < (host_client->netchan.message.maxsize/2))
{
i = buf - bufs - sv.numextrastatics - sv.num_edicts;
if (i >= 255)
break;
ctent = &sv.customtents[i];
buf++;
if (!*ctent->particleeffecttype)
{ //effect isn't registered.
continue;
}
if (host_client->fteprotocolextensions & PEXT_CUSTOMTEMPEFFECTS)
{
MSG_WriteByte(&host_client->netchan.message, svc_customtempent);
MSG_WriteByte(&host_client->netchan.message, 255);
MSG_WriteByte(&host_client->netchan.message, i);
MSG_WriteByte(&host_client->netchan.message, ctent->netstyle);
MSG_WriteString(&host_client->netchan.message, ctent->particleeffecttype);
if (ctent->netstyle & CTE_STAINS)
{
MSG_WriteChar(&host_client->netchan.message, ctent->stain[0]);
MSG_WriteChar(&host_client->netchan.message, ctent->stain[0]);
MSG_WriteChar(&host_client->netchan.message, ctent->stain[0]);
MSG_WriteByte(&host_client->netchan.message, ctent->radius);
}
if (ctent->netstyle & CTE_GLOWS)
{
MSG_WriteByte(&host_client->netchan.message, ctent->dlightrgb[0]);
MSG_WriteByte(&host_client->netchan.message, ctent->dlightrgb[1]);
MSG_WriteByte(&host_client->netchan.message, ctent->dlightrgb[2]);
MSG_WriteByte(&host_client->netchan.message, ctent->dlightradius);
MSG_WriteByte(&host_client->netchan.message, ctent->dlighttime);
}
}
}
}
else if (buf >= bufs)
{
buf = bufs+sv.numextrastatics+sv.num_edicts+255;
}
else
{
if (sv.democausesreconnect)
SZ_Write (&host_client->netchan.message,
sv.demosignon_buffers[buf],
sv.demosignon_buffer_size[buf]);
else
SZ_Write (&host_client->netchan.message,
sv.signon_buffers[buf],
sv.signon_buffer_size[buf]);
buf++;
}
if (buf == bufs+sv.numextrastatics+sv.num_edicts+255)
{ // all done prespawning
MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
MSG_WriteString (&host_client->netchan.message, va("cmd spawn %i\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) );
}
}
void SetUpClientEdict (client_t *cl, edict_t *ent);
/*
==================
SV_Spawn_f
==================
*/
void SV_Spawn_f (void)
{
int i;
client_t *client, *split;
edict_t *ent;
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 && !sv.msgfromdemo)
{
Con_Printf ("SV_Spawn_f from different level\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=0, client = svs.clients ; i<MAX_CLIENTS ; i++, client++)
SV_FullClientUpdateToClient (client, host_client);
// send all current light styles
for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
{
if (sv.democausesreconnect)
{
ClientReliableWrite_Begin (host_client, svc_lightstyle,
3 + (sv.demolightstyles[i] ? strlen(sv.demolightstyles[i]) : 1));
ClientReliableWrite_Byte (host_client, (char)i);
ClientReliableWrite_String (host_client, sv.demolightstyles[i]);
}
else
#ifdef PEXT_LIGHTSTYLECOL
if (host_client->fteprotocolextensions & PEXT_LIGHTSTYLECOL && sv.lightstylecolours[i]!=7)
{
ClientReliableWrite_Begin (host_client, svc_lightstylecol,
3 + (sv.lightstyles[i] ? strlen(sv.lightstyles[i]) : 1));
ClientReliableWrite_Byte (host_client, (char)i);
ClientReliableWrite_Char (host_client, sv.lightstylecolours[i]);
ClientReliableWrite_String (host_client, sv.lightstyles[i]);
}
else
#endif
{
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
for (split = host_client; split; split = split->controlled)
{
ent = split->edict;
if (split->istobeloaded) //minimal setup
{
split->entgravity = ent->v.gravity;
split->maxspeed = ent->v.maxspeed;
}
else
{
SetUpClientEdict(split, ent);
}
//
// 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] = DEFAULT_VIEWHEIGHT;
sv_player->v.movetype = MOVETYPE_NOCLIP;
// search for an info_playerstart to spawn the spectator at
//this is only useful when a mod doesn't nativly support spectators. old qw on nq mods.
for (i=MAX_CLIENTS+1 ; i<sv.num_edicts ; i++)
{
e = EDICT_NUM(svprogfuncs, i);
if (!strcmp(PR_GetString(svprogfuncs, e->v.classname), "info_player_start"))
{
VectorCopy (e->v.origin, sv_player->v.origin);
return;
}
}
}
/*
==================
SV_Begin_f
==================
*/
void SV_Begin_f (void)
{
client_t *split, *oh;
unsigned pmodel = 0, emodel = 0;
int i;
qboolean sendangles=false;
if (host_client->state == cs_spawned)
return; // don't begin again
for (split = host_client; split; split = split->controlled)
split->state = cs_spawned;
// handle the case of a level changing while a client was connecting
if ( atoi(Cmd_Argv(1)) != svs.spawncount && !sv.msgfromdemo)
{
Con_Printf ("SV_Begin_f from different level\n");
SV_New_f ();
return;
}
for (split = host_client; split; split = split->controlled)
{
if (split->istobeloaded)
{
sendangles = true;
split->istobeloaded = false;
}
else
{
if (split->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] = split->spawn_parms[i];
// call the spawn function
pr_global_struct->time = sv.time;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
PR_ExecuteProgram (svprogfuncs, SpectatorConnect);
}
}
else
{
if (svprogfuncs)
{
eval_t *eval, *eval2;
eval = PR_FindGlobal(svprogfuncs, "ClientReEnter", 0);
if (eval && split->spawninfo)
{
globalvars_t *pr_globals = PR_globals(svprogfuncs, PR_CURRENT);
int j;
edict_t *ent;
ent = split->edict;
j = strlen(split->spawninfo);
SV_UnlinkEdict(ent);
svprogfuncs->restoreent(svprogfuncs, split->spawninfo, &j, ent);
eval2 = svprogfuncs->GetEdictFieldValue(svprogfuncs, ent, "stats_restored", NULL);
if (eval2)
eval2->_float = 1;
for (j=0 ; j< NUM_SPAWN_PARMS ; j++)
(&pr_global_struct->parm1)[j] = split->spawn_parms[j];
pr_global_struct->time = sv.time;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, ent);
G_FLOAT(OFS_PARM0) = sv.time - split->spawninfotime;
PR_ExecuteProgram(svprogfuncs, eval->function);
}
else
{
// copy spawn parms out of the client_t
for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
(&pr_global_struct->parm1)[i] = split->spawn_parms[i];
// call the spawn function
pr_global_struct->time = sv.time;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientConnect);
// actually spawn the player
pr_global_struct->time = sv.time;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
PR_ExecuteProgram (svprogfuncs, pr_global_struct->PutClientInServer);
oh = host_client;
SV_PreRunCmd();
{
usercmd_t cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.msec = 0;
#define ANGLE2SHORT(x) (x) * (65536/360.0)
cmd.angles[0] = ANGLE2SHORT(split->edict->v.v_angle[0]);
cmd.angles[1] = ANGLE2SHORT(split->edict->v.v_angle[1]);
cmd.angles[2] = ANGLE2SHORT(split->edict->v.v_angle[2]);
SV_RunCmd(&cmd, false);
}
SV_PostRunCmd();
host_client = oh;
}
}
#ifdef Q2SERVER
else
{
ge->ClientBegin(split->q2edict);
}
#endif
}
}
}
// 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
if (progstype == PROG_QW)
{
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_BroadcastTPrintf (PRINT_HIGH, STL_POSSIBLEMODELCHEAT, host_client->name);
}
// if we are paused, tell the client
if (sv.paused)
{
if (!host_client->isq2client)
{
ClientReliableWrite_Begin (host_client, svc_setpause, 2);
ClientReliableWrite_Byte (host_client, sv.paused);
}
SV_ClientTPrintf(host_client, PRINT_HIGH, STL_SERVERPAUSED);
}
if (sendangles)
{
//
// 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
MSG_WriteByte (&host_client->netchan.message, svc_setangle);
MSG_WriteAngle (&host_client->netchan.message, 0 );
MSG_WriteAngle (&host_client->netchan.message, host_client->edict->v.angles[1] );
MSG_WriteAngle (&host_client->netchan.message, 0 );
}
}
//=============================================================================
int buffer;
void SV_NextChunkedDownload(int chunknum)
{
#define CHUNKSIZE 1024
char buffer[1024];
if (host_client->datagram.cursize + CHUNKSIZE+5 > host_client->datagram.maxsize)
return; //choked!
fseek (host_client->download, chunknum*CHUNKSIZE, SEEK_SET);
fread (buffer, 1, CHUNKSIZE, host_client->download);
MSG_WriteByte(&host_client->datagram, svc_download);
MSG_WriteLong(&host_client->datagram, chunknum);
SZ_Write(&host_client->datagram, buffer, CHUNKSIZE);
}
/*
==================
SV_NextDownload_f
==================
*/
void SV_NextDownload_f (void)
{
qbyte buffer[1024];
int r;
int percent;
int size;
if (!host_client->download)
return;
#ifdef PEXT_CHUNKEDDOWNLOADS
if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS)
{
SV_NextChunkedDownload(atoi(Cmd_Argv(1)));
return;
}
#endif
r = host_client->downloadsize - host_client->downloadcount;
/*
#ifdef PEXT_ZLIBDL
if (host_client->protocolextensions & PEXT_ZLIBDL)
{
if (r>1024) //expect a little more.
r=1024;
}
else
#endif
*/
if (r > 768)
r = 768;
r = fread (buffer, 1, r, host_client->download);
ClientReliableWrite_Begin (host_client, host_client->isq2client?svcq2_download: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;
#ifdef PEXT_ZLIBDL
if (host_client->fteprotocolextensions & PEXT_ZLIBDL)
{
ClientReliableWrite_Byte (host_client, percent+101);
ClientReliableWrite_ZLib (host_client, buffer, r);
}
else
#endif
{
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 VARGS 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 (NS_SERVER, strlen(send)+1, send, where);
}
/*
==================
SV_NextUpload
==================
*/
void SV_NextUpload (void)
{
int percent;
int size;
if (!*host_client->uploadfn)
{
SV_ClientTPrintf(host_client, PRINT_HIGH, STL_UPLOADDENIED);
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;
}
Con_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;
Con_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);
}
}
}
//Use of this function is on name only.
//Be aware that the maps directory should be restricted based on weather the file was from a pack file
//this is to preserve copyright - please do not breach due to a bug.
qboolean SV_AllowDownload (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_demos;
extern cvar_t allow_download_maps;
extern cvar_t allow_download_textures;
extern cvar_t allow_download_pk3s;
extern cvar_t allow_download_wads;
extern cvar_t allow_download_root;
// extern int file_from_pak; // ZOID did file come from pak?
//allowed at all?
if (!allow_download.value)
return false;
//no subdirs?
if (strstr (name, "..")) //no under paths.
return false;
if (*name == '.') //relative is pointless
return false;
if (*name == '/') //no absolute.
return false;
if (strchr(name, '\\')) //no windows paths - grow up lame windows users.
return false;
if (strncmp(name, "maps/", 5) == 0 && (allow_download_maps.value))
return true;
//skins?
if (strncmp(name, "skins/", 6) == 0 && allow_download_skins.value)
return true;
//models
if ((strncmp(name, "progs/", 6) == 0||
strncmp(name, "models/", 7) == 0) && allow_download_models.value)
return true;
//sound
if (strncmp(name, "sound/", 6) == 0 && allow_download_sounds.value)
return true;
//demos
if (strncmp(name, "demos/", 6) == 0 && allow_download_demos.value)
return true;
//textures
if (strncmp(name, "texures/", 8) == 0 && allow_download_textures.value)
return true;
//wads
if (strncmp(name, "wads/", 5) == 0 && allow_download_wads.value)
return true;
if (!strcmp(".wad", COM_FileExtension(name)) && allow_download_wads.value)
return true;
//pk3s.
if (!strcmp(".pk3", COM_FileExtension(name)) && allow_download_pk3s.value)
if (strnicmp(name, "pak", 3)) //don't give out q3 pk3 files.
return true;
//root of gamedir
if (!strchr(name, '/') && !allow_download_root.value)
return false;
//any other subdirs are allowed
return true;
}
/*
==================
SV_BeginDownload_f
==================
*/
void SV_BeginDownload_f(void)
{
char *name;
extern cvar_t allow_download_anymap;
extern int file_from_pak; // ZOID did file come from pak?
name = Cmd_Argv(1);
// hacked by zoid to allow more conrol over download
if (!SV_AllowDownload(name))
{ // don't allow anything with .. path
#ifdef PEXT_CHUNKEDDOWNLOADS
if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS)
{
ClientReliableWrite_Begin (host_client, svc_download, 10+strlen(name));
ClientReliableWrite_Long (host_client, -1);
ClientReliableWrite_Long (host_client, -2);
ClientReliableWrite_String (host_client, name);
}
else
#endif
{
ClientReliableWrite_Begin (host_client, host_client->isq2client?svcq2_download: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
|| (!allow_download_anymap.value && 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);
#ifdef PEXT_CHUNKEDDOWNLOADS
if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS)
{
ClientReliableWrite_Begin (host_client, svc_download, 10+strlen(name));
ClientReliableWrite_Long (host_client, -1);
ClientReliableWrite_Long (host_client, -1);
ClientReliableWrite_String (host_client, name);
}
else
#endif
{
ClientReliableWrite_Begin (host_client, host_client->isq2client?svcq2_download:svc_download, 4);
ClientReliableWrite_Short (host_client, -1);
ClientReliableWrite_Byte (host_client, 0);
}
return;
}
#ifdef PEXT_CHUNKEDDOWNLOADS
if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS)
{
ClientReliableWrite_Begin (host_client, svc_download, 10+strlen(name));
ClientReliableWrite_Long (host_client, -1);
ClientReliableWrite_Long (host_client, host_client->downloadsize);
ClientReliableWrite_String (host_client, name);
}
#endif
SV_NextDownload_f ();
SV_EndRedirect();
Con_Printf ("Downloading %s to %s\n", name, host_client->name);
}
//=============================================================================
/*
==================
SV_SayOne_f
==================
*/
void SV_SayOne_f (void)
{
char text[1024];
client_t *to;
int i;
char *s, *s2;
int clnum=-1;
if (Cmd_Argc () < 3)
return;
while((to = SV_GetClientForString(Cmd_Argv(1), &clnum)))
{
if (host_client->spectator)
{
if (!sv_spectalk.value || to->spectator)
sprintf (text, "[SPEC] {%s}:", host_client->name);
else
continue;
}
else
sprintf (text, "{%s}:", host_client->name);
if (host_client->ismuted)
{
SV_ClientTPrintf(host_client, PRINT_CHAT,
STL_YOUAREMUTED);
return;
}
for (i = 2; ; i++)
{
s = Cmd_Argv(i);
if (!*s)
break;
if (strlen(text) + strlen(s) + 2 >= sizeof(text)-1)
break;
strcat(text, " ");
strcat(text, s);
}
//filter out '\n' and '\r'
s = text;
s2 = text;
while(*s2)
{
if (*s2 == '\r' || *s2 == '\n')
{
s2++;
continue;
}
*s = *s2;
s++;
s2++;
}
*s = '\0';
strcat(text, "\n");
SV_ClientPrintf(to, PRINT_CHAT, "%s", text);
}
if (clnum==-1) //none found
{
SV_ClientTPrintf(host_client, PRINT_CHAT, STL_NAMEDCLIENTDOESNTEXIST);
return;
}
}
/*
==================
SV_Say
==================
*/
void SV_Say (qboolean team)
{
client_t *client;
int j, tmp;
char *p;
char text[1024];
char t1[32], *t2;
int cls = 0;
qboolean sent[MAX_CLIENTS]; //so we don't send to the same splitscreen connection twice. (it's ugly)
int cln;
char *s, *s2;
if (Cmd_Argc () < 2)
return;
Sys_ServerActivity();
memset(sent, 0, sizeof(sent));
if (team)
{
Q_strncpyz (t1, Info_ValueForKey (host_client->userinfo, "team"), sizeof(t1));
}
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 (host_client->ismuted)
{
SV_ClientTPrintf(host_client, PRINT_CHAT,
STL_MUTEDCHAT);
return;
}
if (fp_messages)
{
if (!sv.paused && realtime<host_client->lockedtill)
{
SV_ClientTPrintf(host_client, PRINT_CHAT,
STL_FLOODPROTTIME,
(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_ClientTPrintf(host_client, PRINT_CHAT,
STL_FLOODPROTACTIVE, 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;
}
if (strlen(text)+strlen(p)+2 >= sizeof(text)-10)
{
SV_ClientTPrintf(host_client, PRINT_CHAT, STL_BUFFERPROTECTION);
return;
}
if (svprogfuncs)
if (PR_QCChat(p, team)) //true if handled.
return;
Q_strcat(text, p);
//filter out '\n' and '\r'
if (sv_chatfilter.value)
{
s = text;
s2 = text;
while(*s2)
{
if (*s2 == '\r' || *s2 == '\n')
{
s2++;
continue;
}
*s = *s2;
s++;
s2++;
}
*s = '\0';
}
Q_strcat(text, "\n");
Sys_Printf ("%s", text);
for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
{
if (client->state != cs_spawned && client->state != cs_connected)
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
}
}
cls |= 1 << j;
//make sure we don't send the say to the same client 20 times due to splitscreen
if (client->controller)
cln = client->controller - svs.clients;
else
cln = client - svs.clients;
if (sent[cln])
continue;
else
sent[cln] = true;
SV_ClientPrintf(client, PRINT_CHAT, "%s", text);
}
if (!sv.mvdrecording || !cls)
return;
// non-team messages should be seen allways, even if not tracking any player
if (!team && ((host_client->spectator && sv_spectalk.value) || !host_client->spectator))
{
MVDWrite_Begin (dem_all, 0, strlen(text)+3);
}
else
MVDWrite_Begin (dem_multiple, cls, strlen(text)+3);
MSG_WriteByte ((sizebuf_t*)demo.dbuf, svc_print);
MSG_WriteByte ((sizebuf_t*)demo.dbuf, PRINT_CHAT);
MSG_WriteString ((sizebuf_t*)demo.dbuf, 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;
if (sv.demofile)
{
for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
{
if (!*sv.recordedplayer[j].userinfo)
continue;
ClientReliableWrite_Begin (host_client, svc_updateping, 4);
ClientReliableWrite_Byte (host_client, j);
ClientReliableWrite_Short (host_client, sv.recordedplayer[j].ping);
ClientReliableWrite_Begin (host_client, svc_updatepl, 4);
ClientReliableWrite_Byte (host_client, j);
ClientReliableWrite_Byte (host_client, sv.recordedplayer[j].pl);
}
return;
}
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_ClientTPrintf (host_client, PRINT_HIGH, STL_NOSUISIDEWHENDEAD);
return;
}
pr_global_struct->time = sv.time;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientKill);
}
/*
==================
SV_TogglePause
==================
*/
void SV_TogglePause (void)
{
int i;
client_t *cl;
sv.paused ^= 1;
// send notification to all clients
for (i=0, cl = svs.clients ; i<MAX_CLIENTS ; i++, cl++)
{
if (!cl->state)
continue;
if (!cl->isq2client && !cl->controller)
{
ClientReliableWrite_Begin (cl, svc_setpause, 2);
ClientReliableWrite_Byte (cl, sv.paused);
}
}
}
/*
==================
SV_Pause_f
==================
*/
void SV_Pause_f (void)
{
if (!pausable.value)
{
SV_ClientTPrintf (host_client, PRINT_HIGH, STL_CANTPAUSE);
return;
}
if (host_client->spectator && !svs.demoplayback)
{
SV_ClientTPrintf (host_client, PRINT_HIGH, STL_CANTPAUSESPEC);
return;
}
SV_TogglePause();
if (sv.paused)
SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTPAUSED, host_client->name);
else
SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTUNPAUSED, host_client->name);
}
/*
=================
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_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTDROPPED, 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 && !sv.demofile)
return;
if (Cmd_Argc() != 2)
{
// turn off tracking
host_client->spec_track = 0;
ent = EDICT_NUM(svprogfuncs, host_client - svs.clients + 1);
tent = EDICT_NUM(svprogfuncs, 0);
ent->v.goalentity = EDICT_TO_PROG(svprogfuncs, tent);
return;
}
i = atoi(Cmd_Argv(1));
if (*sv.recordedplayer[i].userinfo)
{
host_client->spec_track = i+1;
return;
}
if (i < 0 || i >= MAX_CLIENTS || svs.clients[i].state != cs_spawned ||
svs.clients[i].spectator)
{
SV_ClientTPrintf (host_client, PRINT_HIGH, STL_INVALIDTRACKCLIENT);
host_client->spec_track = 0;
ent = EDICT_NUM(svprogfuncs, host_client - svs.clients + 1);
tent = EDICT_NUM(svprogfuncs, 0);
ent->v.goalentity = EDICT_TO_PROG(svprogfuncs, tent);
return;
}
host_client->spec_track = i + 1; // now tracking
ent = EDICT_NUM(svprogfuncs, host_client - svs.clients + 1);
tent = EDICT_NUM(svprogfuncs, i + 1);
ent->v.goalentity = EDICT_TO_PROG(svprogfuncs, tent);
}
/*
=================
SV_Rate_f
Change the bandwidth estimate for a client
=================
*/
void SV_Rate_f (void)
{
extern cvar_t sv_maxrate;
int rate;
if (Cmd_Argc() != 2)
{
SV_ClientTPrintf (host_client, PRINT_HIGH, STL_CURRENTRATE,
host_client->rate);
return;
}
rate = atoi(Cmd_Argv(1));
if (rate < 500)
rate = 500;
if (rate > sv_maxrate.value)
rate = sv_maxrate.value;
SV_ClientTPrintf (host_client, PRINT_HIGH, STL_RATESETTO, rate);
host_client->rate = rate;
}
/*
=================
SV_Msg_f
Change the message level for a client
=================
*/
void SV_Msg_f (void)
{
if (Cmd_Argc() != 2)
{
SV_ClientTPrintf (host_client, PRINT_HIGH, STL_CURRENTMSGLEVEL,
host_client->messagelevel);
return;
}
host_client->messagelevel = atoi(Cmd_Argv(1));
SV_ClientTPrintf (host_client, PRINT_HIGH, STL_MSGLEVELSET, 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);
if (progstype != PROG_QW && !strcmp(Cmd_Argv(1), "bottomcolor"))
{
sv_player->v.team = atoi(Cmd_Argv(2))+1;
}
PR_ClientUserInfoChanged(Cmd_Argv(1), oldval, Cmd_Argv(2));
if (*Cmd_Argv(1) == '_')
return;
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_BroadcastTPrintf (PRINT_HIGH, STL_SNAPREFUSED, host_client->name);
}
}
//3 votes per player.
typedef struct voteinfo_s {
struct voteinfo_s *next;
float timeout;
int clientid;
char command[1];
} voteinfo_t;
voteinfo_t *voteinfo;
void VoteAdd (char *cmd, int id)
{
voteinfo_t *vote;
vote = Z_Malloc(sizeof(voteinfo_t)+strlen(cmd)); //null term is part of voteinfo_t
strcpy(vote->command, cmd);
vote->clientid = id;
vote->timeout = realtime+votetime.value*60;
vote->next = voteinfo;
voteinfo = vote;
}
void VoteRemoveCommands(char *command, int id) //all of one command
{
voteinfo_t *vote, *prev;
prev = NULL;
for (vote = voteinfo; vote; vote = vote->next)
{
if ((!command || !strcmp(vote->command, command)) && (vote->clientid == id || id == -1))
{
if (prev)
prev->next = vote->next;
else
voteinfo = vote->next;
Z_Free(vote);
VoteRemoveCommands(command, id);
return;
}
else
prev = vote;
}
}
void VoteFlushAll(void)
{
VoteRemoveCommands(NULL, -1);
}
int VoteCount(char *command, int id) //all of one command
{
voteinfo_t *vote;
int num=0;
for (vote = voteinfo; vote; vote = vote->next)
{
if (!command || !strcmp(vote->command, command))
{
if (vote->clientid == id || id == -1)
num++;
}
}
return num;
}
void VoteCheckTimes(void)
{
voteinfo_t *vote, *prev;
prev = NULL;
for (vote = voteinfo; vote; vote = vote->next)
{
if (vote->timeout < realtime)
{
if (prev)
prev->next = vote->next;
else
voteinfo = vote->next;
Z_Free(vote);
}
else
prev = vote;
}
}
void SV_Vote_f (void)
{
char *command = Cmd_Args();
char *base;
int id = host_client->userid;
int num;
int totalusers = 0;
qboolean passes;
if (!votelevel.value)
{
Con_TPrintf(STL_NOVOTING);
return;
}
if (host_client->ismuted)
{
Con_TPrintf(STL_MUTEDVOTE);
return;
}
Cmd_ExecLevel = votelevel.value;
base = command;
while(*base>' ')
base++;
if (*base)
*base = '\0';
else
base = NULL;
if (strchr(command, ';') || !strcmp(command, "if"))
{
Con_TPrintf(STL_BADVOTE);
return;
}
num = Cmd_Level(command);
if (base)
*base = ' ';
if (num != Cmd_ExecLevel)
{
Con_TPrintf(STL_BADVOTE);
return;
}
VoteCheckTimes();
for (num = 0; num < sv.allocated_client_slots; num++)
if (svs.clients[num].state == cs_spawned)
totalusers++;
if (VoteCount(command, id))
{
VoteRemoveCommands(command, id);
Con_TPrintf(STL_OLDVOTEREMOVED);
return;
}
if (VoteCount(NULL, id)>=3)
{
VoteRemoveCommands(NULL, id);
Con_TPrintf(STL_VOTESREMOVED);
}
num = VoteCount(command, -1)+1;
passes = true;
if (votepercent.value < 0 && (float)(totalusers-num) >= 0.5*totalusers)
passes = false;
if (votepercent.value >= 0 && num <= totalusers*votepercent.value/100)
passes = false;
if (num < voteminimum.value)
passes = false;
if (passes) //>min number of votes, and meets the percent required
{
SV_BroadcastTPrintf(PRINT_HIGH, STL_FINALVOTE, host_client->name, command);
VoteRemoveCommands(command, -1);
Cbuf_AddText(command, votelevel.value);
//Cmd_ExecuteString (command, votelevel.value);
return;
}
else //otherwise, try later.
{
SV_BroadcastTPrintf(PRINT_HIGH, STL_VOTE, host_client->name, command);
VoteAdd(command, id);
}
}
extern qboolean sv_allow_cheats;
void Cmd_Notarget_f (void)
{
if (!sv_allow_cheats)
{
Con_Printf ("Cheats are not allowed on this server\n");
return;
}
if ((int) (sv_player->v.flags = (int) sv_player->v.flags ^ FL_NOTARGET) & FL_NOTARGET)
SV_ClientPrintf (host_client, PRINT_HIGH, "notarget ON\n");
else
SV_ClientPrintf (host_client, PRINT_HIGH, "notarget OFF\n");
}
//Sets client to godmode
void Cmd_God_f (void)
{
if (!sv_allow_cheats)
{
Con_Printf ("Cheats are not allowed on this server\n");
return;
}
if ((int) (sv_player->v.flags = (int) sv_player->v.flags ^ FL_GODMODE) & FL_GODMODE)
SV_ClientPrintf (host_client, PRINT_HIGH, "godmode ON\n");
else
SV_ClientPrintf (host_client, PRINT_HIGH, "godmode OFF\n");
}
void Cmd_Give_f (void)
{
char *t;
int v;
if (!sv_allow_cheats)
{
Con_Printf ("Cheats are not allowed on this server\n");
return;
}
t = Cmd_Argv(1);
v = atoi (Cmd_Argv(2));
if (strlen(t) == 1 && (Cmd_Argc() == 3 || (*t>='0' && *t <= '9')))
{
switch (t[0])
{
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
sv_player->v.items = (int) sv_player->v.items | IT_SHOTGUN<< (t[0] - '2');
break;
case 's':
sv_player->v.ammo_shells = v;
break;
case 'n':
sv_player->v.ammo_nails = v;
break;
case 'r':
sv_player->v.ammo_rockets = v;
break;
case 'h':
sv_player->v.health = v;
break;
case 'c':
sv_player->v.ammo_cells = v;
break;
}
}
else if (host_client->netchan.remote_address.type == NA_LOOPBACK) //we don't want clients doing nasty things... like setting movetype 3123
{
int oldself;
oldself = pr_global_struct->self;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
Con_Printf("Result: %s\n", svprogfuncs->EvaluateDebugString(svprogfuncs, Cmd_Args()));
pr_global_struct->self = oldself;
}
}
void Cmd_Noclip_f (void)
{
if (!sv_allow_cheats)
{
Con_Printf ("Cheats are not allowed on this server\n");
return;
}
if (sv_player->v.movetype != MOVETYPE_NOCLIP)
{
sv_player->v.movetype = MOVETYPE_NOCLIP;
sv_player->v.solid = SOLID_TRIGGER;
SV_ClientPrintf (host_client, PRINT_HIGH, "noclip ON\n");
}
else
{
sv_player->v.movetype = MOVETYPE_WALK;
if (sv_player->v.health > 0)
sv_player->v.solid = SOLID_SLIDEBOX;
else
sv_player->v.solid = SOLID_NOT;
SV_ClientPrintf (host_client, PRINT_HIGH, "noclip OFF\n");
}
}
void Cmd_Fly_f (void)
{
if (!sv_allow_cheats)
{
Con_Printf ("Cheats are not allowed on this server\n");
return;
}
if (sv_player->v.movetype != MOVETYPE_FLY)
{
sv_player->v.movetype = MOVETYPE_FLY;
SV_ClientPrintf (host_client, PRINT_HIGH, "flymode ON\n");
}
else
{
sv_player->v.movetype = MOVETYPE_WALK;
if (sv_player->v.health > 0)
sv_player->v.solid = SOLID_SLIDEBOX;
else
sv_player->v.solid = SOLID_NOT;
SV_ClientPrintf (host_client, PRINT_HIGH, "flymode OFF\n");
}
}
/*
====================
Host_SetPos_f UDC
By Alex Shadowalker
====================
*/
void Cmd_SetPos_f(void)
{
if (!sv_allow_cheats)
{
Con_Printf ("Cheats are not allowed on this server\n");
return;
}
if (Cmd_Argc() != 4)
{
Con_Printf ("setpos %i %i %i\n", (int)sv_player->v.origin[0], (int)sv_player->v.origin[1], (int)sv_player->v.origin[2]);
return;
}
if (sv_player->v.movetype != MOVETYPE_NOCLIP)
{
sv_player->v.movetype = MOVETYPE_NOCLIP;
Con_Printf("noclip on\n");
}
sv_player->v.origin[0] = atof(Cmd_Argv(1));
sv_player->v.origin[1] = atof(Cmd_Argv(2));
sv_player->v.origin[2] = atof(Cmd_Argv(3));
SV_LinkEdict (sv_player, false);
}
void ED_ClearEdict (progfuncs_t *progfuncs, edict_t *e);
void SetUpClientEdict (client_t *cl, edict_t *ent)
{
extern int pr_teamfield;
ED_ClearEdict(svprogfuncs, ent);
ED_Spawned(ent);
ent->isfree = false;
ent->v.colormap = NUM_FOR_EDICT(svprogfuncs, ent);
ent->v.netname = PR_SetString(svprogfuncs, cl->name);
if (pr_teamfield)
((string_t *)&ent->v)[pr_teamfield] = (string_t)PR_SetString(svprogfuncs, cl->team);
ent->v.gravity = cl->entgravity = 1.0;
ent->v.maxspeed = cl->maxspeed = sv_maxspeed.value;
ent->v.movetype = MOVETYPE_NOCLIP;
}
/*
==================
Cmd_Join_f
Set client to player mode without reconnecting
==================
*/
void Cmd_Join_f (void)
{
int i;
client_t *cl;
int numclients;
extern cvar_t maxclients;
if (host_client->state != cs_spawned)
return;
if (!host_client->spectator)
return; // already a player
if (!(host_client->zquake_extensions & Z_EXT_JOIN_OBSERVE))
{
Con_Printf ("Your QW client doesn't support this command\n");
return;
}
if (password.string[0] && stricmp(password.string, "none"))
{
Con_Printf ("This server requires a %s password. Please disconnect, set the password and reconnect as %s.\n", "player", "player");
return;
}
// count players already on server
numclients = 0;
for (i=0,cl=svs.clients ; i<sv.allocated_client_slots ; i++,cl++)
{
if (cl->state != cs_free && !cl->spectator)
numclients++;
}
if (numclients >= maxclients.value)
{
Con_Printf ("Can't join, all player slots full\n");
return;
}
// call the prog function for removing a client
// this will set the body to a dead frame, among other things
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
if (SpectatorDisconnect)
PR_ExecuteProgram (svprogfuncs, SpectatorDisconnect);
host_client->old_frags = 0;
SetUpClientEdict (host_client, host_client->edict);
// turn the spectator into a player
host_client->spectator = false;
Info_RemoveKey (host_client->userinfo, "*spectator");
// FIXME, bump the client's userid?
// call the progs to get default spawn parms for the new client
PR_ExecuteProgram (svprogfuncs, pr_global_struct->SetNewParms);
for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
host_client->spawn_parms[i] = (&pr_global_struct->parm1)[i];
// call the spawn function
pr_global_struct->time = sv.time;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientConnect);
// actually spawn the player
pr_global_struct->time = sv.time;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
PR_ExecuteProgram (svprogfuncs, pr_global_struct->PutClientInServer);
// send notification to all clients
host_client->sendinfo = true;
}
/*
==================
Cmd_Observe_f
Set client to spectator mode without reconnecting
==================
*/
void Cmd_Observe_f (void)
{
int i;
client_t *cl;
int numspectators;
extern cvar_t maxspectators, spectator_password;
if (host_client->state != cs_spawned)
return;
if (host_client->spectator)
return; // already a spectator
if (!(host_client->zquake_extensions & Z_EXT_JOIN_OBSERVE))
{
Con_Printf ("Your QW client doesn't support this command\n");
return;
}
if (spectator_password.string[0] && stricmp(spectator_password.string, "none"))
{
Con_Printf ("This server requires a %s password. Please disconnect, set the password and reconnect as %s.\n", "spectator", "spectator");
return;
}
// count spectators already on server
numspectators = 0;
for (i=0,cl=svs.clients ; i<sv.allocated_client_slots ; i++,cl++) {
if (cl->state != cs_free && cl->spectator)
numspectators++;
}
if (numspectators >= maxspectators.value)
{
Con_Printf ("Can't join, all spectator slots full\n");
return;
}
// call the prog function for removing a client
// this will set the body to a dead frame, among other things
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientDisconnect);
host_client->old_frags = 0;
SetUpClientEdict (host_client, host_client->edict);
// turn the player into a spectator
host_client->spectator = true;
Info_SetValueForStarKey (host_client->userinfo, "*spectator", "1", MAX_INFO_STRING);
// FIXME, bump the client's userid?
// call the progs to get default spawn parms for the new client
PR_ExecuteProgram (svprogfuncs, pr_global_struct->SetNewParms);
for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
host_client->spawn_parms[i] = (&pr_global_struct->parm1)[i];
SV_SpawnSpectator ();
// call the spawn function
if (SpectatorConnect)
{
pr_global_struct->time = sv.time;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
PR_ExecuteProgram (svprogfuncs, SpectatorConnect);
}
// send notification to all clients
host_client->sendinfo = true;
}
void SV_MVDList_f (void);
void SV_MVDInfo_f (void);
typedef struct
{
char *name;
void (*func) (void);
qboolean noqchandling;
} ucmd_t;
ucmd_t ucmds[] =
{
{"new", SV_New_f, true},
{"pk3list", SV_PK3List_f, true},
{"modellist", SV_Modellist_f, true},
{"soundlist", SV_Soundlist_f, true},
{"prespawn", SV_PreSpawn_f, true},
{"spawn", SV_Spawn_f, true},
{"begin", SV_Begin_f, true},
{"join", Cmd_Join_f},
{"observe", Cmd_Observe_f},
{"drop", SV_Drop_f},
{"disconnect", 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},
{"sayone", SV_SayOne_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},
{"vote", SV_Vote_f},
#ifdef SVRANKING
{"topten", Rank_ListTop10_f},
#endif
{"god", Cmd_God_f},
{"give", Cmd_Give_f},
{"noclip", Cmd_Noclip_f},
{"fly", Cmd_Fly_f},
{"notarget", Cmd_Notarget_f},
{"setpos", Cmd_SetPos_f},
// {"stopdownload", SV_StopDownload_f},
{"demolist", SV_MVDList_f},
{"demoinfo", SV_MVDInfo_f},
{NULL, NULL}
};
#ifdef Q2SERVER
ucmd_t ucmdsq2[] = {
{"new", SV_New_f, true},
{"configstrings", SVQ2_ConfigStrings_f, true},
{"baselines", SVQ2_BaseLines_f, true},
{"begin", SV_Begin_f, true},
{"setinfo", SV_SetInfo_f, true},
{"serverinfo", SV_ShowServerinfo_f, true},
{"download", SV_BeginDownload_f, true},
{"nextdl", SV_NextDownload_f, true},
{"nextserver", SVQ2_NextServer_f, true},
{"vote", SV_Vote_f, true},
#ifdef SVRANKING
{"topten", Rank_ListTop10_f, true},
#endif
{"drop", SV_Drop_f, true},
{"disconnect", SV_Drop_f, true},
{NULL, NULL}
};
#endif
/*
==================
SV_ExecuteUserCommand
==================
*/
void SV_ExecuteUserCommand (char *s, qboolean fromQC)
{
ucmd_t *u;
client_t *oldhost = host_client;
Con_DPrintf("Client command: %s\n", s);
Cmd_TokenizeString (s, false, false);
sv_player = host_client->edict;
Cmd_ExecLevel=1;
if (atoi(Cmd_Argv(0))>0) //now see if it's meant to be from a slave client
{
int pnum = atoi(Cmd_Argv(0));
client_t *s;
for (s = host_client; s; s = s->controlled)
{
if (!--pnum)
{
host_client = s;
break;
}
}
sv_player = host_client->edict;
Cmd_ShiftArgs(1, false);
}
#ifdef Q2SERVER
if (host_client->isq2client)
u = ucmdsq2;
else
#endif
u=ucmds;
for ( ; u->name ; u++)
if (!strcmp (Cmd_Argv(0), u->name) )
{
if (!fromQC && !u->noqchandling)
if (PR_KrimzonParseCommand(s)) //KRIMZON_SV_PARSECLIENTCOMMAND has the opertunity to parse out certain commands.
{
host_client = oldhost;
return;
}
SV_BeginRedirect (RD_CLIENT, host_client->language);
u->func ();
host_client = oldhost;
SV_EndRedirect ();
return;
}
if (!u->name)
{
if (!fromQC)
if (PR_UserCmd(s)) //Q2 and MVDSV command handling only happens if the engine didn't recognise it.
{
host_client = oldhost;
return;
}
if (sv_cmdlikercon.value && host_client->rankid)
{
char remaining[1024];
int i;
rankstats_t stats;
if (!Rank_GetPlayerStats(host_client->rankid, &stats))
{
host_client = oldhost;
return;
}
Con_Printf ("cmd from %s:\n%s\n"
, host_client->name, net_message.data+4);
SV_BeginRedirect (RD_CLIENT, host_client->language);
remaining[0] = 0;
for (i=0 ; i<Cmd_Argc() ; i++)
{
if (strlen(remaining)+strlen(Cmd_Argv(i))>=sizeof(remaining)-1)
{
Con_Printf("cmd was too long\n");
host_client = oldhost;
SV_EndRedirect ();
Con_Printf ("cmd from %s:\n%s\n"
, NET_AdrToString (net_from), "Was too long - possible buffer overflow attempt");
return;
}
strcat (remaining, Cmd_Argv(i) );
strcat (remaining, " ");
}
Cmd_ExecuteString (remaining, stats.trustlevel);
host_client = oldhost;
SV_EndRedirect ();
return;
}
Con_Printf ("Bad user command: %s\n", Cmd_Argv(0));
}
host_client = oldhost;
SV_EndRedirect ();
}
#ifdef NQPROT
void SVNQ_Spawn_f (void)
{
extern cvar_t sv_gravity;
int i;
client_t *client;
edict_t *ent;
if (host_client->state != cs_connected)
{
Con_Printf ("spawn not valid -- allready spawned\n");
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=0, client = svs.clients; i<sv.allocated_client_slots ; 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;
if (host_client->istobeloaded) //minimal setup
{
host_client->entgravity = ent->v.gravity*sv_gravity.value;
host_client->maxspeed = ent->v.maxspeed;
}
else
{
memset (&ent->v, 0, pr_edict_size-svprogparms.edictsize);
ED_Spawned(ent);
ent->v.colormap = NUM_FOR_EDICT(svprogfuncs, ent);
ent->v.team = 0; // FIXME
ent->v.netname = PR_SetString(svprogfuncs, host_client->name);
host_client->entgravity = ent->v.gravity = 1.0;
host_client->entgravity*=sv_gravity.value;
host_client->maxspeed = ent->v.maxspeed = sv_maxspeed.value;
}
//
// force stats to be updated
//
memset (host_client->stats, 0, sizeof(host_client->stats));
ClientReliableWrite_Begin (host_client, svc_updatestat, 6);
ClientReliableWrite_Byte (host_client, STAT_TOTALSECRETS);
ClientReliableWrite_Long (host_client, pr_global_struct->total_secrets);
ClientReliableWrite_Begin (host_client, svc_updatestat, 6);
ClientReliableWrite_Byte (host_client, STAT_TOTALMONSTERS);
ClientReliableWrite_Long (host_client, pr_global_struct->total_monsters);
ClientReliableWrite_Begin (host_client, svc_updatestat, 6);
ClientReliableWrite_Byte (host_client, STAT_SECRETS);
ClientReliableWrite_Long (host_client, pr_global_struct->found_secrets);
ClientReliableWrite_Begin (host_client, svc_updatestat, 6);
ClientReliableWrite_Byte (host_client, STAT_MONSTERS);
ClientReliableWrite_Long (host_client, pr_global_struct->killed_monsters);
SZ_Write (&host_client->netchan.message, sv.signon.data, sv.signon.cursize);
MSG_WriteByte (&host_client->netchan.message, svc_signonnum);
MSG_WriteByte (&host_client->netchan.message, 3);
host_client->send_message = true;
}
void SVNQ_Begin_f (void)
{
unsigned pmodel = 0, emodel = 0;
int i;
qboolean sendangles=false;
if (host_client->state == cs_spawned)
return; // don't begin again
host_client->state = cs_spawned;
if (host_client->istobeloaded)
{
sendangles = true;
host_client->istobeloaded = false;
}
else
{
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(svprogfuncs, sv_player);
PR_ExecuteProgram (svprogfuncs, 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(svprogfuncs, sv_player);
PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientConnect);
// actually spawn the player
pr_global_struct->time = sv.time;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
PR_ExecuteProgram (svprogfuncs, 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
if (sv_playermodelchecks.value)
{
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_BroadcastTPrintf (PRINT_HIGH, STL_POSSIBLEMODELCHEAT, host_client->name);
}
// if we are paused, tell the client
if (sv.paused)
{
if (!host_client->isq2client)
{
ClientReliableWrite_Begin (host_client, svc_setpause, 2);
ClientReliableWrite_Byte (host_client, sv.paused);
}
SV_ClientTPrintf(host_client, PRINT_HIGH, STL_SERVERPAUSED);
}
if (sendangles)
{
//
// 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
MSG_WriteByte (&host_client->netchan.message, svc_setangle);
MSG_WriteAngle (&host_client->netchan.message, 0 );
MSG_WriteAngle (&host_client->netchan.message, host_client->edict->v.angles[1] );
MSG_WriteAngle (&host_client->netchan.message, 0 );
}
SZ_Write (&host_client->netchan.message, sv.signon.data, sv.signon.cursize);
MSG_WriteByte (&host_client->netchan.message, svc_signonnum);
MSG_WriteByte (&host_client->netchan.message, 4);
host_client->send_message = true;
}
void SVNQ_PreSpawn_f (void)
{
edict_t *ent;
entity_state_t *state;
int i, e;
if (host_client->state != cs_connected)
{
Con_Printf ("prespawn not valid -- allready spawned\n");
return;
}
for (e = 1; e < sv.num_edicts; e++)
{
ent = EDICT_NUM(svprogfuncs, e);
state = &ent->baseline;
if (!state->number || !state->modelindex)
{ //ent doesn't have a baseline
continue;
}
if (!ent)
{
MSG_WriteByte(&host_client->netchan.message, svc_spawnbaseline);
MSG_WriteShort (&host_client->netchan.message, e);
MSG_WriteByte (&host_client->netchan.message, 0);
MSG_WriteByte (&host_client->netchan.message, 0);
MSG_WriteByte (&host_client->netchan.message, 0);
MSG_WriteByte (&host_client->netchan.message, 0);
for (i=0 ; i<3 ; i++)
{
MSG_WriteCoord(&host_client->netchan.message, 0);
MSG_WriteAngle(&host_client->netchan.message, 0);
}
}
else
{
MSG_WriteByte(&host_client->netchan.message, svc_spawnbaseline);
MSG_WriteShort (&host_client->netchan.message, e);
MSG_WriteByte (&host_client->netchan.message, state->modelindex&255);
MSG_WriteByte (&host_client->netchan.message, state->frame);
MSG_WriteByte (&host_client->netchan.message, (int)state->colormap);
MSG_WriteByte (&host_client->netchan.message, (int)state->skinnum);
for (i=0 ; i<3 ; i++)
{
MSG_WriteCoord(&host_client->netchan.message, state->origin[i]);
MSG_WriteAngle(&host_client->netchan.message, state->angles[i]);
}
}
}
for (i = 0; i < sv.num_signon_buffers; i++)
SZ_Write (&host_client->netchan.message, sv.signon_buffers[i], sv.signon_buffer_size[i]);
MSG_WriteByte (&host_client->netchan.message, svc_signonnum);
MSG_WriteByte (&host_client->netchan.message, 2);
host_client->send_message = true;
}
void SVNQ_NQInfo_f (void)
{
Cmd_TokenizeString(va("setinfo \"%s\" \"%s\"\n", Cmd_Argv(0), Cmd_Argv(1)), false, false);
SV_SetInfo_f();
}
void SVNQ_NQColour_f (void)
{
int top;
int bottom;
int playercolor;
if (Cmd_Argc() == 2)
top = bottom = atoi(Cmd_Argv(1));
else
{
top = atoi(Cmd_Argv(1));
bottom = atoi(Cmd_Argv(2));
}
top &= 15;
if (top > 13)
top = 13;
bottom &= 15;
if (bottom > 13)
bottom = 13;
playercolor = top*16 + bottom;
if (progstype != PROG_QW)
host_client->edict->v.team = bottom + 1;
Info_SetValueForKey(host_client->userinfo, "topcolor", va("%i", top), MAX_INFO_STRING);
Info_SetValueForKey(host_client->userinfo, "bottomcolor", va("%i", bottom), MAX_INFO_STRING);
switch(bottom)
{
case 4:
Info_SetValueForKey(host_client->userinfo, "team", "red", MAX_INFO_STRING);
break;
case 14:
Info_SetValueForKey(host_client->userinfo, "team", "blue", MAX_INFO_STRING);
break;
default:
Info_SetValueForKey(host_client->userinfo, "team", va("t%i", bottom+1), MAX_INFO_STRING);
break;
}
SV_ExtractFromUserinfo (host_client);
MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
MSG_WriteString (&sv.reliable_datagram, "topcolor");
MSG_WriteString (&sv.reliable_datagram, Info_ValueForKey(host_client->userinfo, "topcolor"));
MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
MSG_WriteString (&sv.reliable_datagram, "bottomcolor");
MSG_WriteString (&sv.reliable_datagram, Info_ValueForKey(host_client->userinfo, "bottomcolor"));
MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
MSG_WriteString (&sv.reliable_datagram, "team");
MSG_WriteString (&sv.reliable_datagram, Info_ValueForKey(host_client->userinfo, "team"));
MSG_WriteByte (&sv.nqreliable_datagram, svc_updatecolors);
MSG_WriteByte (&sv.nqreliable_datagram, host_client - svs.clients);
MSG_WriteByte (&sv.nqreliable_datagram, playercolor);
}
ucmd_t nqucmds[] =
{
{"status", NULL},
{"god", Cmd_God_f},
{"give", Cmd_Give_f},
{"notarget", Cmd_Notarget_f},
{"fly", NULL},
{"noclip", Cmd_Noclip_f},
{"name", SVNQ_NQInfo_f},
{"say", SV_Say_f},
{"say_team", SV_Say_Team_f},
{"tell", SV_SayOne_f},
{"color", SVNQ_NQColour_f},
{"kill", SV_Kill_f},
{"pause", SV_Pause_f},
{"spawn", SVNQ_Spawn_f, true},
{"begin", SVNQ_Begin_f, true},
{"prespawn", SVNQ_PreSpawn_f, true},
{"kick", NULL},
{"ping", NULL},
{"ban", NULL},
{"vote", SV_Vote_f},
#ifdef SVRANKING
{"topten", Rank_ListTop10_f},
#endif
{NULL, NULL}
};
void SVNQ_ExecuteUserCommand (char *s)
{
client_t *oldhost = host_client;
ucmd_t *u;
Cmd_TokenizeString (s, false, false);
sv_player = host_client->edict;
Cmd_ExecLevel=1;
for (u=nqucmds ; u->name ; u++)
{
if (!strcmp (Cmd_Argv(0), u->name) )
{
if (/*!fromQC && */!u->noqchandling)
if (PR_UserCmd(s))
{
host_client = oldhost;
return;
}
if (!u->func)
{
SV_BeginRedirect (RD_CLIENT, host_client->language);
Con_Printf("Command was disabled\n");
SV_EndRedirect ();
}
else
{
SV_BeginRedirect (RD_CLIENT, host_client->language);
u->func ();
SV_EndRedirect ();
}
host_client = oldhost;
return;
}
}
if (!u->name)
Con_Printf("%s tried to \"%s\"\n", host_client->name, s);
}
#endif
int implevels[256];
qboolean SV_FiltureImpulse(int imp, int level)
{
if (imp < 0 || imp > 255)
return true; //erm
if (implevels[imp] > level)
return false;
return true;
}
void SV_FilterImpulseInit(void)
{
char buffer[1024];
char *s;
int lev;
int imp;
memset(implevels, 0, sizeof(implevels));
s = COM_LoadStackFile("impfiltr.cfg", buffer, sizeof(buffer));
if (!s)
Con_Printf("impfiltr.cfg not found. Impulse filters are disabled\n");
while(s)
{
s = COM_Parse(s);
if (!s)
return;
imp = atoi(com_token);
s = COM_Parse(s);
if (!s)
{
Con_Printf("Unexpected eof in impfiltr.cfg\n");
return;
}
lev = atoi(com_token);
if (imp > 255 || imp < 0 || lev < 0 || lev > RESTRICT_MAX)
Con_Printf("impfiltr.cfg - bad paramters\n");
else
implevels[imp] = lev;
}
}
/*
===========================================================================
USER CMD EXECUTION
===========================================================================
*/
#ifdef SERVERONLY
/*
===============
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;
}
#endif
//============================================================================
vec3_t pmove_mins, pmove_maxs;
/*
====================
AddLinksToPmove
====================
*/
void AddLinksToPmove ( areanode_t *node )
{
int Q1_HullPointContents (hull_t *hull, int num, vec3_t p);
link_t *l, *next;
edict_t *check;
int pl;
int i;
physent_t *pe;
float os, omt;
hull_t *hull;
vec3_t offset;
pl = EDICT_TO_PROG(svprogfuncs, 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)
break;
pe = &pmove.physents[pmove.numphysent];
pe->notouch = !((int)sv_player->v.dimension_solid & (int)check->v.dimension_hit);
if (!((int)sv_player->v.dimension_hit & (int)check->v.dimension_solid))
continue;
pmove.numphysent++;
VectorCopy (check->v.origin, pe->origin);
VectorCopy (check->v.angles, pe->angles);
pe->info = NUM_FOR_EDICT(svprogfuncs, check);
if (check->v.solid == SOLID_BSP)
{
if(progstype != PROG_H2)
pe->angles[0]*=-1; //quake is wierd. I guess someone fixed it hexen2... or my code is buggy or something...
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 (sv_player->v.mins[2] != 24) //crouching/dead
for (l = node->trigger_edicts.next ; l != &node->trigger_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_LADDER)
{
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 (!((int)sv_player->v.dimension_hit & (int)check->v.dimension_solid))
continue;
// check->v.model = "a";
os = check->v.solid;
omt = check->v.movetype;
check->v.solid = SOLID_BSP;
check->v.movetype = MOVETYPE_PUSH;
hull = SV_HullForEntity (check, 0, sv_player->v.mins, sv_player->v.maxs, offset);
check->v.movetype = omt;
check->v.solid = os;
// test the point
if ( hull->funcs.HullPointContents (hull, sv_player->v.origin) == FTECONTENTS_SOLID )
sv_player->v.fteflags = (int)sv_player->v.fteflags | FF_LADDER; //touch that ladder!
}
}
// 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(svprogfuncs, sv_player);
for (e=1 ; e<sv.num_edicts ; e++)
{
check = EDICT_NUM(svprogfuncs, e);
if (check->isfree)
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);
VectorCopy (check->v.angles, pe->angles);
pmove.physents[pmove.numphysent].info = e;
if (check->v.solid == SOLID_BSP)
pe->model = sv.models[(int)(check->v.modelindex)];
else
{
pe->angles[0] = pe->angles[1] = pe->angles[2] = 0;
pe->model = NULL;
VectorCopy (check->v.mins, pe->mins);
VectorCopy (check->v.maxs, pe->maxs);
}
if (++pmove.numphysent == MAX_PHYSENTS)
break;
}
}
}
int SV_PMTypeForClient (client_t *cl)
{
if (sv.demostatevalid)
{ //force noclip... This does create problems for closing demos.
if (cl->zquake_extensions & Z_EXT_PM_TYPE_NEW)
return PM_SPECTATOR;
return PM_OLD_SPECTATOR;
}
if (cl->edict->v.movetype == MOVETYPE_NOCLIP)
{
if (cl->zquake_extensions & Z_EXT_PM_TYPE_NEW)
return PM_SPECTATOR;
return PM_OLD_SPECTATOR;
}
if (cl->edict->v.movetype == MOVETYPE_FLY)
return PM_FLY;
if (cl->edict->v.movetype == MOVETYPE_NONE)
{
if (sv_brokenmovetypes.value)
return PM_NORMAL;
return PM_NONE;
}
if (cl->edict->v.health <= 0)
return PM_DEAD;
return PM_NORMAL;
}
/*
===========
SV_PreRunCmd
===========
Done before running a player command. Clears the touch array
*/
qbyte playertouch[(MAX_EDICTS+7)/8];
void SV_PreRunCmd(void)
{
memset(playertouch, 0, sizeof(playertouch));
}
/*
===========
SV_RunCmd
===========
*/
void SV_RunCmd (usercmd_t *ucmd, qboolean recurse)
{
edict_t *ent;
int i, n;
int oldmsec;
double tmp_time;
qboolean jumpable;
extern cvar_t sv_gravity;
#ifdef Q2SERVER
if (!svprogfuncs)
{
ge->ClientThink (host_client->q2edict, (q2usercmd_t*)ucmd);
return;
}
#endif
// DMW copied this KK hack copied from QuakeForge anti-cheat
// (also extra inside parm on all SV_RunCmds that follow)
// To prevent a infinite loop
if (!recurse) {
host_client->msecs += ucmd->msec;
if ((tmp_time = realtime - host_client->last_check) >= sv_cheatspeedchecktime.value) {
tmp_time = tmp_time * 1000.0 * sv_cheatpc.value/100.0;
if (host_client->msecs > tmp_time) {
host_client->msec_cheating++;
SV_BroadcastTPrintf(PRINT_HIGH,
STL_SPEEDCHEATPOSSIBLE,
host_client->msecs, tmp_time,
host_client->msec_cheating, host_client->name);
if (host_client->msec_cheating >= 2) {
SV_BroadcastTPrintf(PRINT_HIGH,
STL_SPEEDCHEATKICKED,
host_client->name, NET_AdrToString(host_client->netchan.remote_address));
SV_DropClient(host_client);
}
}
host_client->msecs = 0;
host_client->last_check = realtime;
}
}
// end KK hack copied from QuakeForge anti-cheat
//it's amazing how code get's copied around...
cmd = *ucmd;
// chop up very long commands
if (cmd.msec > 50)
{
oldmsec = ucmd->msec;
cmd.msec = oldmsec/2;
SV_RunCmd (&cmd, true);
cmd.msec = oldmsec/2 + (oldmsec&1); //give them back thier msec.
cmd.impulse = 0;
SV_RunCmd (&cmd, true);
return;
}
host_frametime = ucmd->msec * 0.001;
if (host_frametime > 0.1)
host_frametime = 0.1;
if (sv.demostatevalid)
{ //spectators watching MVDs do not affect the running progs.
player_mins[0] = -16;
player_mins[1] = -16;
player_mins[2] = -24;
player_maxs[0] = 16;
player_maxs[1] = 16;
player_maxs[2] = 32;
pmove.angles[0] = SHORT2ANGLE(ucmd->angles[0]);
pmove.angles[1] = SHORT2ANGLE(ucmd->angles[1]);
pmove.angles[2] = SHORT2ANGLE(ucmd->angles[2]);
VectorCopy (host_client->specorigin, pmove.origin);
VectorCopy (host_client->specvelocity, pmove.velocity);
if (host_client->zquake_extensions & Z_EXT_PM_TYPE_NEW)
pmove.pm_type = PM_SPECTATOR;
else
pmove.pm_type = PM_OLD_SPECTATOR;
pmove.jump_held = host_client->jump_held;
pmove.jump_msec = 0;
pmove.waterjumptime = 0;
pmove.numphysent = 1;
pmove.physents[0].model = sv.worldmodel;
pmove.cmd = *ucmd;
pmove.hullnum = SV_HullNumForPlayer(0, player_mins, player_maxs);
movevars.entgravity = 0;
movevars.maxspeed = 0;
movevars.bunnyspeedcap = pm_bunnyspeedcap.value;
movevars.ktjump = pm_ktjump.value;
movevars.slidefix = (pm_slidefix.value != 0);
movevars.airstep = (pm_airstep.value != 0);
movevars.walljump = (pm_walljump.value);
for (i=0 ; i<3 ; i++)
{
pmove_mins[i] = pmove.origin[i] - 256;
pmove_maxs[i] = pmove.origin[i] + 256;
}
PM_PlayerMove ();
VectorCopy (pmove.origin, host_client->specorigin);
VectorCopy (pmove.velocity, host_client->specvelocity);
return;
}
#ifdef SVCHAT
if (SV_ChatMove(ucmd->impulse))
{
ucmd->buttons = 0;
ucmd->impulse = 0;
ucmd->forwardmove = ucmd->sidemove = ucmd->upmove = 0;
}
#endif
if (!sv_player->v.fixangle)
{
sv_player->v.v_angle[0] = SHORT2ANGLE(ucmd->angles[0]);
sv_player->v.v_angle[1] = SHORT2ANGLE(ucmd->angles[1]);
sv_player->v.v_angle[2] = SHORT2ANGLE(ucmd->angles[2]);
}
if (progstype == PROG_H2)
sv_player->v.light_level = 128; //hmm... HACK!!!
sv_player->v.button0 = ucmd->buttons & 1;
sv_player->v.button2 = (ucmd->buttons >> 1) & 1;
if (pr_allowbutton1.value) //many mods use button1 - it's just a wasted field to many mods. So only work it if the cvar allows.
sv_player->v.button1 = ((ucmd->buttons >> 2) & 1);
// DP_INPUTBUTTONS
sv_player->v.button3 = ((ucmd->buttons >> 2) & 1);
sv_player->v.button4 = ((ucmd->buttons >> 3) & 1);
sv_player->v.button5 = ((ucmd->buttons >> 4) & 1);
sv_player->v.button6 = ((ucmd->buttons >> 5) & 1);
sv_player->v.button7 = ((ucmd->buttons >> 6) & 1);
sv_player->v.button8 = ((ucmd->buttons >> 7) & 1);
if (ucmd->impulse && SV_FiltureImpulse(ucmd->impulse, host_client->trustlevel))
sv_player->v.impulse = ucmd->impulse;
if (host_client->iscuffed)
{
sv_player->v.impulse = 0;
sv_player->v.button0 = 0;
}
sv_player->v.movement[0] = ucmd->forwardmove * host_frametime;
sv_player->v.movement[1] = ucmd->sidemove * host_frametime;
sv_player->v.movement[2] = ucmd->upmove * host_frametime;
SV_CheckVelocity(sv_player);
//
// 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;
}
if (!host_client->spectator)
{
vec_t oldvz;
pr_global_struct->frametime = host_frametime;
pr_global_struct->time = sv.time;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
oldvz = sv_player->v.velocity[2];
if (progstype != PROG_QW)
{
#define FL_JUMPRELEASED 4096
jumpable = (int)sv_player->v.flags & FL_JUMPRELEASED;
pmove.waterjumptime = sv_player->v.teleport_time;
if (pmove.waterjumptime > sv.time)
sv_player->v.flags = (int)sv_player->v.flags | FL_WATERJUMP;
sv_player->v.teleport_time = sv.time + pmove.waterjumptime;
}
else
jumpable = false;
PR_ExecuteProgram (svprogfuncs, pr_global_struct->PlayerPreThink);
if (progstype != PROG_QW)
{
if (sv_player->v.velocity[2] == 225 && sv_player->v.teleport_time > sv.time)
{
sv_player->v.velocity[2] = oldvz;
// Con_Printf("Waterjump detected\n");
}
sv_player->v.flags = (int)sv_player->v.flags & ~FL_WATERJUMP;
sv_player->v.teleport_time = pmove.waterjumptime;
if (jumpable && !((int)sv_player->v.flags & FL_JUMPRELEASED)) //hmm... a jump was hit.
sv_player->v.velocity[2] -= 270;
}
SV_RunThink (sv_player);
}
// memset(&pmove, 0, sizeof(pmove));
// memset(&movevars, 0, sizeof(movevars));
player_mins[0] = sv_player->v.mins[0];
player_mins[1] = sv_player->v.mins[1];
player_mins[2] = sv_player->v.mins[2];
player_maxs[0] = sv_player->v.maxs[0];
player_maxs[1] = sv_player->v.maxs[1];
player_maxs[2] = sv_player->v.maxs[2];
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.pm_type = SV_PMTypeForClient (host_client);
pmove.jump_held = host_client->jump_held;
pmove.jump_msec = 0;
if (progstype != PROG_QW) //this is just annoying.
pmove.waterjumptime = sv_player->v.teleport_time - sv.time;
else
pmove.waterjumptime = sv_player->v.teleport_time;
pmove.numphysent = 1;
pmove.physents[0].model = sv.worldmodel;
pmove.cmd = *ucmd;
if (sv.worldmodel->fromgame == fg_quake2 || sv.worldmodel->fromgame == fg_quake3)
pmove.hullnum = ((int)sv_player->v.fteflags&FF_CROUCHING)?3:1;
else
pmove.hullnum = SV_HullNumForPlayer(sv_player->v.hull, sv_player->v.mins, sv_player->v.maxs);
movevars.entgravity = host_client->entgravity/movevars.gravity;
movevars.maxspeed = host_client->maxspeed;
movevars.bunnyspeedcap = pm_bunnyspeedcap.value;
movevars.ktjump = pm_ktjump.value;
movevars.slidefix = (pm_slidefix.value != 0);
movevars.airstep = (pm_airstep.value != 0);
movevars.walljump = (pm_walljump.value);
if (sv_player->v.hasted)
movevars.maxspeed*=sv_player->v.hasted;
for (i=0 ; i<3 ; i++)
{
pmove_mins[i] = pmove.origin[i] - 256;
pmove_maxs[i] = pmove.origin[i] + 256;
}
sv_player->v.fteflags = (int)sv_player->v.fteflags & ~FF_LADDER; //assume not touching ladder trigger
#if 1
AddLinksToPmove ( sv_areanodes );
#else
AddAllEntsToPmove ();
#endif
if ((int)sv_player->v.fteflags & FF_LADDER)
pmove.onladder = true;
else
pmove.onladder = false;
#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
PM_PlayerMove ();
#endif
host_client->jump_held = pmove.jump_held;
if (progstype != PROG_QW) //this is just annoying.
sv_player->v.teleport_time = pmove.waterjumptime + sv.time;
else
sv_player->v.teleport_time = pmove.waterjumptime;
sv_player->v.waterlevel = pmove.waterlevel;
if (pmove.watertype & FTECONTENTS_SOLID)
sv_player->v.watertype = Q1CONTENTS_SOLID;
else if (pmove.watertype & FTECONTENTS_SKY)
sv_player->v.watertype = Q1CONTENTS_SKY;
else if (pmove.watertype & FTECONTENTS_LAVA)
sv_player->v.watertype = Q1CONTENTS_LAVA;
else if (pmove.watertype & FTECONTENTS_SLIME)
sv_player->v.watertype = Q1CONTENTS_SLIME;
else if (pmove.watertype & FTECONTENTS_WATER)
sv_player->v.watertype = Q1CONTENTS_WATER;
else
sv_player->v.watertype = Q1CONTENTS_EMPTY;
if (pmove.onground)
{
sv_player->v.flags = (int)sv_player->v.flags | FL_ONGROUND;
sv_player->v.groundentity = EDICT_TO_PROG(svprogfuncs, EDICT_NUM(svprogfuncs, pmove.physents[pmove.groundent].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);
player_mins[0] = -16;
player_mins[1] = -16;
player_mins[2] = -24;
player_maxs[0] = 16;
player_maxs[1] = 16;
player_maxs[2] = 32;
if (!host_client->spectator)
{
// link into place and touch triggers
SV_LinkEdict (sv_player, true);
/* for (i = 0; i < pmove.numphysent; i++)
{
}
*/
// touch other objects
for (i=0 ; i<pmove.numtouch ; i++)
{
if (pmove.physents[pmove.touchindex[i]].notouch)
continue;
n = pmove.physents[pmove.touchindex[i]].info;
ent = EDICT_NUM(svprogfuncs, n);
if (!ent->v.touch || (playertouch[n/8]&(1<<(n%8))))
continue;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, ent);
pr_global_struct->other = EDICT_TO_PROG(svprogfuncs, sv_player);
pr_global_struct->time = sv.time;
PR_ExecuteProgram (svprogfuncs, 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 (!svprogfuncs)
return;
if (!host_client->spectator) {
pr_global_struct->time = sv.time;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
PR_ExecuteProgram (svprogfuncs, pr_global_struct->PlayerPostThink);
SV_RunNewmis ();
} else if (SpectatorThink) {
pr_global_struct->time = sv.time;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
PR_ExecuteProgram (svprogfuncs, SpectatorThink);
}
}
/*
===================
SV_ExecuteClientMessage
The current net_message is parsed for the given client
===================
*/
void SV_ClientThink (void);
void SV_ExecuteClientMessage (client_t *cl)
{
client_t *split;
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;
qbyte checksum, calculatedChecksum;
int seq_hash, i;
// calc ping time
if (cl->frames)
{ //split screen doesn't always have frames.
frame = &cl->frames[cl->netchan.incoming_acknowledged & UPDATE_MASK];
if (cl->lastsequence_acknoledged + UPDATE_BACKUP > cl->netchan.incoming_acknowledged)
frame->ping_time = realtime - frame->senttime; //no more phenomanally low pings please
for (i = cl->lastsequence_acknoledged+1; i < cl->netchan.incoming_acknowledged; i++)
SV_CSQC_DroppedPacket(cl, i);
cl->lastsequence_acknoledged = cl->netchan.incoming_acknowledged;
}
else
return; //shouldn't happen...
// 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
if (cl->frames)
{ //split screen doesn't always have frames.
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;
// 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 ();
haveannothergo:
if (c == -1)
break;
switch (c)
{
default:
Con_Printf ("SV_ReadClientMessage: unknown command char %i\n", c);
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 = (qbyte)MSG_ReadByte ();
// read loss percentage
cl->lossage = MSG_ReadByte();
for (split = cl; cl; cl = cl->controlled) //FIXME
{
host_client = cl;
cl->localtime = sv.time;
sv_player = cl->edict;
MSG_ReadDeltaUsercmd (&nullcmd, &oldest);
MSG_ReadDeltaUsercmd (&oldest, &oldcmd);
MSG_ReadDeltaUsercmd (&oldcmd, &newcmd);
if ( cl->state == cs_spawned )
{
if (split == cl)
{
// 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);
for (; cl; cl = cl->controlled) //FIXME
{
MSG_ReadDeltaUsercmd (&nullcmd, &oldest);
MSG_ReadDeltaUsercmd (&oldest, &oldcmd);
MSG_ReadDeltaUsercmd (&oldcmd, &newcmd);
}
break;;
}
}
if (cl->iscrippled)
{
cl->lastcmd.forwardmove = 0; //hmmm.... does this work well enough?
oldest.forwardmove = 0;
newcmd.forwardmove = 0;
cl->lastcmd.sidemove = 0;
oldest.sidemove = 0;
newcmd.sidemove = 0;
cl->lastcmd.upmove = 0;
oldest.upmove = 0;
newcmd.upmove = 0;
}
if (!sv.paused)
{
if (sv_nomsec.value || SV_PlayerPhysicsQC)
{
if (!sv_player->v.fixangle)
{
sv_player->v.v_angle[0] = newcmd.angles[0]* (360.0/65536);
sv_player->v.v_angle[1] = newcmd.angles[1]* (360.0/65536);
sv_player->v.v_angle[2] = newcmd.angles[2]* (360.0/65536);
}
if (newcmd.impulse)// && SV_FiltureImpulse(newcmd.impulse, host_client->trustlevel))
sv_player->v.impulse = newcmd.impulse;
sv_player->v.button0 = newcmd.buttons & 1;
sv_player->v.button2 = (newcmd.buttons >> 1) & 1;
if (pr_allowbutton1.value) //many mods use button1 - it's just a wasted field to many mods. So only work it if the cvar allows.
sv_player->v.button1 = ((newcmd.buttons >> 2) & 1);
// DP_INPUTBUTTONS
sv_player->v.button3 = ((newcmd.buttons >> 2) & 1);
sv_player->v.button4 = ((newcmd.buttons >> 3) & 1);
sv_player->v.button5 = ((newcmd.buttons >> 4) & 1);
sv_player->v.button6 = ((newcmd.buttons >> 5) & 1);
sv_player->v.button7 = ((newcmd.buttons >> 6) & 1);
sv_player->v.button8 = ((newcmd.buttons >> 7) & 1);
cl->lastcmd = newcmd;
cl->lastcmd.buttons = 0; // avoid multiple fires on lag
if (msg_badread)
{
Con_Printf ("SV_ReadClientMessage: badread\n");
SV_DropClient (cl);
return;
}
c = MSG_ReadByte ();
if (c != clc_move)
{
host_client = cl = split;
sv_player = cl->edict;
goto haveannothergo;
}
continue;
}
SV_PreRunCmd();
if (net_drop < 20)
{
while (net_drop > 2)
{
SV_RunCmd (&cl->lastcmd, false);
net_drop--;
}
if (net_drop > 1)
SV_RunCmd (&oldest, false);
if (net_drop > 0)
SV_RunCmd (&oldcmd, false);
}
SV_RunCmd (&newcmd, false);
SV_PostRunCmd();
}
cl->lastcmd = newcmd;
cl->lastcmd.buttons = 0; // avoid multiple fires on lag
}
if (msg_badread)
{
Con_Printf ("SV_ReadClientMessage: badread\n");
SV_DropClient (cl);
return;
}
c = MSG_ReadByte ();
if (c != clc_move)
{
host_client = cl = split;
sv_player = cl->edict;
goto haveannothergo;
}
}
host_client = cl = split;
sv_player = cl->edict;
break;
case clc_stringcmd:
s = MSG_ReadString ();
SV_ExecuteUserCommand (s, false);
host_client = cl;
sv_player = cl->edict;
break;
case clc_tmove:
o[0] = MSG_ReadCoord();
o[1] = MSG_ReadCoord();
o[2] = MSG_ReadCoord();
// only allowed by spectators
if (sv.mvdplayback)
{
VectorCopy(o, host_client->specorigin);
}
else if (host_client->spectator)
{
VectorCopy(o, sv_player->v.origin);
SV_LinkEdict(sv_player, false);
}
break;
case clc_upload:
SV_NextUpload();
break;
}
}
host_client = NULL;
sv_player = NULL;
}
#ifdef Q2SERVER
void SVQ2_ExecuteClientMessage (client_t *cl)
{
enum clcq2_ops_e c;
char *s;
usercmd_t oldest, oldcmd, newcmd;
q2client_frame_t *frame;
qboolean move_issued = false; //only allow one move command
int checksumIndex;
qbyte checksum, calculatedChecksum;
int seq_hash;
int lastframe;
// calc ping time
frame = &cl->q2frames[cl->netchan.incoming_acknowledged & Q2UPDATE_MASK];
// 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->q2frames[cl->netchan.outgoing_sequence & Q2UPDATE_MASK].senttime = realtime;
// cl->q2frames[cl->netchan.outgoing_sequence & UPDATE_MASK].ping_time = -1;
host_client = cl;
sv_player = host_client->edict;
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 ("SVQ2_ExecuteClientMessage: badread\n");
SV_DropClient (cl);
return;
}
c = MSG_ReadByte ();
if (c == -1)
break;
switch (c)
{
default:
Con_Printf ("SV_ReadClientMessage: unknown command char %i\n", c);
SV_DropClient (cl);
return;
case clcq2_nop:
break;
case clcq2_move:
if (move_issued)
return; // someone is trying to cheat...
move_issued = true;
checksumIndex = MSG_GetReadCount();
checksum = (qbyte)MSG_ReadByte ();
lastframe = MSG_ReadLong();
if (lastframe != host_client->delta_sequence) {
cl->delta_sequence = lastframe;
}
MSGQ2_ReadDeltaUsercmd (&nullcmd, &oldest);
MSGQ2_ReadDeltaUsercmd (&oldest, &oldcmd);
MSGQ2_ReadDeltaUsercmd (&oldcmd, &newcmd);
if ( cl->state != cs_spawned )
break;
// if the checksum fails, ignore the rest of the packet
calculatedChecksum = Q2COM_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 (cl->iscrippled)
{
cl->lastcmd.forwardmove = 0; //hmmm.... does this work well enough?
oldest.forwardmove = 0;
newcmd.forwardmove = 0;
cl->lastcmd.sidemove = 0;
oldest.sidemove = 0;
newcmd.sidemove = 0;
cl->lastcmd.upmove = 0;
oldest.upmove = 0;
newcmd.upmove = 0;
}
if (!sv.paused)
{
/*if (sv_nomsec.value)
{
cmd = newcmd;
SV_ClientThink ();
cl->lastcmd = newcmd;
cl->lastcmd.buttons = 0; // avoid multiple fires on lag
break;
}*/
SV_PreRunCmd();
if (net_drop < 20)
{
while (net_drop > 2)
{
SV_RunCmd (&cl->lastcmd, false);
net_drop--;
}
if (net_drop > 1)
SV_RunCmd (&oldest, false);
if (net_drop > 0)
SV_RunCmd (&oldcmd, false);
}
SV_RunCmd (&newcmd, false);
SV_PostRunCmd();
}
cl->lastcmd = newcmd;
cl->lastcmd.buttons = 0; // avoid multiple fires on lag
break;
case clcq2_userinfo:
s = MSG_ReadString ();
break;
case clcq2_stringcmd:
s = MSG_ReadString ();
SV_ExecuteUserCommand (s, false);
if (cl->state == cs_zombie)
return; // disconnect command
break;
}
}
}
#endif
#ifdef NQPROT
void SVNQ_ReadClientMove (usercmd_t *move)
{
int i;
int bits;
MSG_ReadFloat (); //message time (nq uses it to time pings - qw ignores it)
// read current angles
for (i=0 ; i<3 ; i++)
{
host_client->edict->v.v_angle[i] = MSG_ReadAngle ();
move->angles[i] = (host_client->edict->v.v_angle[i] * 256*256)/360;
}
// read movement
move->forwardmove = MSG_ReadShort ();
move->sidemove = MSG_ReadShort ();
move->upmove = MSG_ReadShort ();
move->msec=100;
// read buttons
bits = MSG_ReadByte ();
move->buttons = bits;
i = MSG_ReadByte ();
if (i)
move->impulse = i;
if (i && SV_FiltureImpulse(i, host_client->trustlevel))
host_client->edict->v.impulse = i;
host_client->edict->v.button0 = bits & 1;
host_client->edict->v.button2 = (bits >> 1) & 1;
if (pr_allowbutton1.value) //many mods use button1 - it's just a wasted field to many mods. So only work it if the cvar allows.
host_client->edict->v.button1 = ((bits >> 2) & 1);
// DP_INPUTBUTTONS
host_client->edict->v.button3 = ((bits >> 2) & 1);
host_client->edict->v.button4 = ((bits >> 3) & 1);
host_client->edict->v.button5 = ((bits >> 4) & 1);
host_client->edict->v.button6 = ((bits >> 5) & 1);
host_client->edict->v.button7 = ((bits >> 6) & 1);
host_client->edict->v.button8 = ((bits >> 7) & 1);
}
void SVNQ_ExecuteClientMessage (client_t *cl)
{
int c;
char *s;
client_frame_t *frame;
int seq_hash;
MSG_BeginReading ();
// 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;
// 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 %i\n", c);
SV_DropClient (cl);
return;
case clc_disconnect:
SV_DropClient (cl);
break;
case clc_nop:
break;
case clc_delta:
cl->delta_sequence = MSG_ReadByte ();
break;
case clc_move:
SVNQ_ReadClientMove (&host_client->lastcmd);
// cmd = host_client->lastcmd;
// SV_ClientThink();
break;
case clc_stringcmd:
s = MSG_ReadString ();
SVNQ_ExecuteUserCommand (s);
break;
}
}
}
#endif
/*
==============
SV_UserInit
==============
*/
void SV_UserInit (void)
{
#ifdef SERVERONLY
Cvar_Register (&cl_rollspeed, "Prediction stuff");
Cvar_Register (&cl_rollangle, "Prediction stuff");
#endif
Cvar_Register (&sv_chatfilter, cvargroup_serverpermissions);
Cvar_Register (&sv_spectalk, cvargroup_servercontrol);
Cvar_Register (&sv_mapcheck, cvargroup_servercontrol);
Cvar_Register (&sv_cheatpc, cvargroup_servercontrol);
Cvar_Register (&sv_cheatspeedchecktime, cvargroup_servercontrol);
Cvar_Register (&sv_playermodelchecks, cvargroup_servercontrol);
Cvar_Register (&sv_cmdlikercon, cvargroup_serverpermissions);
Cvar_Register (&votelevel, sv_votinggroup);
Cvar_Register (&voteminimum, sv_votinggroup);
Cvar_Register (&votepercent, sv_votinggroup);
Cvar_Register (&votetime, sv_votinggroup);
Cvar_Register (&sv_brokenmovetypes, "");
Cvar_Register (&sv_edgefriction, "netquake compatability");
}
static vec3_t forward, right, up, wishdir;
static float *origin, *velocity, *angles;
static float wishspeed;
extern cvar_t sv_accelerate, sv_friction;
static qboolean onground;
/*
==================
SV_UserFriction
==================
*/
void SV_UserFriction (void)
{
extern cvar_t sv_stopspeed;
float *vel;
float speed, newspeed, control;
vec3_t start, stop;
float friction;
trace_t trace;
vel = velocity;
speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]);
if (!speed)
return;
// if the leading edge is over a dropoff, increase friction
start[0] = stop[0] = origin[0] + vel[0]/speed*16;
start[1] = stop[1] = origin[1] + vel[1]/speed*16;
start[2] = origin[2] + sv_player->v.mins[2];
stop[2] = start[2] - 34;
trace = SV_Move (start, vec3_origin, vec3_origin, stop, true, sv_player);
if (trace.fraction == 1.0)
friction = sv_friction.value*sv_edgefriction.value;
else
friction = sv_friction.value;
// val = GetEdictFieldValue(sv_player, "friction", &frictioncache);
// if (val && val->_float)
// friction *= val->_float;
// apply friction
control = speed < sv_stopspeed.value ? sv_stopspeed.value : speed;
newspeed = speed - host_frametime*control*friction;
if (newspeed < 0)
newspeed = 0;
newspeed /= speed;
vel[0] = vel[0] * newspeed;
vel[1] = vel[1] * newspeed;
vel[2] = vel[2] * newspeed;
}
void SV_Accelerate (void)
{
int i;
float addspeed, accelspeed, currentspeed;
currentspeed = DotProduct (velocity, wishdir);
addspeed = wishspeed - currentspeed;
if (addspeed <= 0)
return;
accelspeed = sv_accelerate.value*host_frametime*wishspeed;
if (accelspeed > addspeed)
accelspeed = addspeed;
for (i=0 ; i<3 ; i++)
velocity[i] += accelspeed*wishdir[i];
}
void SV_AirAccelerate (vec3_t wishveloc)
{
int i;
float addspeed, wishspd, accelspeed, currentspeed;
wishspd = VectorNormalize (wishveloc);
if (wishspd > 30)
wishspd = 30;
currentspeed = DotProduct (velocity, wishveloc);
addspeed = wishspd - currentspeed;
if (addspeed <= 0)
return;
// accelspeed = sv_accelerate.value * host_frametime;
accelspeed = sv_accelerate.value*wishspeed * host_frametime;
if (accelspeed > addspeed)
accelspeed = addspeed;
for (i=0 ; i<3 ; i++)
velocity[i] += accelspeed*wishveloc[i];
}
/*
===================
SV_AirMove
===================
*/
void SV_AirMove (void)
{
int i;
vec3_t wishvel;
float fmove, smove;
float scale, maxspeed;
AngleVectors (sv_player->v.angles, forward, right, up);
fmove = cmd.forwardmove;
smove = cmd.sidemove;
// hack to not let you back into teleporter
if (sv.time < sv_player->v.teleport_time && fmove < 0)
fmove = 0;
for (i=0 ; i<3 ; i++)
wishvel[i] = forward[i]*fmove + right[i]*smove;
if ( (int)sv_player->v.movetype != MOVETYPE_WALK)
wishvel[2] = cmd.upmove;
else
wishvel[2] = 0;
VectorCopy (wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
// val = GetEdictFieldValue(sv_player, "scale", &scalecache);
// if (!val || !val->_float)
scale = 1;
// else
// scale = val->_float;
maxspeed=sv_player->v.maxspeed;//FIXME: This isn't fully compatable code...
if (sv_player->v.hasted)
maxspeed*=sv_player->v.hasted;
if (wishspeed > maxspeed*scale)
{
VectorScale (wishvel, maxspeed/wishspeed, wishvel);
wishspeed = maxspeed*scale;
}
if ( sv_player->v.movetype == MOVETYPE_NOCLIP)
{ // noclip
VectorCopy (wishvel, velocity);
}
else if ( onground )
{
SV_UserFriction ();
SV_Accelerate ();
}
else
{ // not on ground, so little effect on velocity
SV_AirAccelerate (wishvel);
}
}
void SV_WaterMove (void)
{
int i;
vec3_t wishvel;
float speed, newspeed, wishspeed, addspeed, accelspeed;
float scale;
float maxspeed;
//
// user intentions
//
AngleVectors (sv_player->v.v_angle, forward, right, up);
for (i=0 ; i<3 ; i++)
wishvel[i] = forward[i]*cmd.forwardmove + right[i]*cmd.sidemove;
if (!cmd.forwardmove && !cmd.sidemove && !cmd.upmove)
wishvel[2] -= 60; // drift towards bottom
else
wishvel[2] += cmd.upmove;
wishspeed = Length(wishvel);
// val = GetEdictFieldValue(sv_player, "scale", &scalecache);
// if (!val || !val->_float)
scale = 1;
// else
// scale = val->_float;
// val = GetEdictFieldValue(sv_player, "maxspeed", &maxspeedcache);
// if (val && val->_float)
// maxspeed = sv_maxspeed.value*val->_float;
// else
maxspeed = host_client->maxspeed;
if (wishspeed > maxspeed*scale)
{
VectorScale (wishvel, maxspeed/wishspeed, wishvel);
wishspeed = maxspeed*scale;
}
wishspeed *= 0.7;
//
// water friction
//
speed = Length (velocity);
if (speed)
{
// val = GetEdictFieldValue(sv_player, "friction", &frictioncache);
// if (val&&val->_float)
// newspeed = speed - host_frametime * speed * sv_friction.value*val->_float;
// else
newspeed = speed - host_frametime * speed * sv_friction.value;
if (newspeed < 0)
newspeed = 0;
VectorScale (velocity, newspeed/speed, velocity);
}
else
newspeed = 0;
//
// water acceleration
//
if (!wishspeed)
return;
addspeed = wishspeed - newspeed;
if (addspeed <= 0)
return;
VectorNormalize (wishvel);
accelspeed = sv_accelerate.value * wishspeed * host_frametime;
if (accelspeed > addspeed)
accelspeed = addspeed;
for (i=0 ; i<3 ; i++)
velocity[i] += accelspeed * wishvel[i];
}
void SV_WaterJump (void)
{
if (sv.time > sv_player->v.teleport_time
|| !sv_player->v.waterlevel)
{
sv_player->v.flags = (int)sv_player->v.flags & ~FL_WATERJUMP;
sv_player->v.teleport_time = 0;
}
sv_player->v.velocity[0] = sv_player->v.movedir[0];
sv_player->v.velocity[1] = sv_player->v.movedir[1];
}
void SV_ClientThink (void)
{
vec3_t v_angle;
cmd = host_client->lastcmd;
sv_player = host_client->edict;
sv_player->v.movement[0] = cmd.forwardmove;
sv_player->v.movement[1] = cmd.sidemove;
sv_player->v.movement[2] = cmd.upmove;
if (SV_PlayerPhysicsQC)
{
pr_global_struct->time = sv.time;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
PR_ExecuteProgram (svprogfuncs, SV_PlayerPhysicsQC);
return;
}
if (sv_player->v.movetype == MOVETYPE_NONE)
return;
onground = (int)sv_player->v.flags & FL_ONGROUND;
origin = sv_player->v.origin;
velocity = sv_player->v.velocity;
// DropPunchAngle ();
//
// if dead, behave differently
//
if (sv_player->v.health <= 0)
return;
//
// angles
// show 1/3 the pitch angle and all the roll angle
angles = sv_player->v.angles;
VectorCopy (sv_player->v.v_angle, v_angle);
// VectorAdd (sv_player->v.v_angle, sv_player->v.punchangle, v_angle);
angles[ROLL] = V_CalcRoll (sv_player->v.angles, sv_player->v.velocity)*4;
if (!sv_player->v.fixangle)
{
angles[PITCH] = -v_angle[PITCH]/3;
angles[YAW] = v_angle[YAW];
}
if ( (int)sv_player->v.flags & FL_WATERJUMP )
{
SV_WaterJump ();
return;
}
//
// walk
//
if ( (sv_player->v.waterlevel >= 2)
&& (sv_player->v.movetype != MOVETYPE_NOCLIP) )
{
SV_WaterMove ();
return;
}
SV_AirMove ();
}
#endif