9ae7e2621d
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
5052 lines
123 KiB
C
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
|