mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-30 15:41:49 +00:00
9f7ca44459
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@742 fc73d0e0-1445-4013-8a0c-d673dee63da5
4911 lines
119 KiB
C
4911 lines
119 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);
|
|
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)
|
|
{
|
|
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; ; 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; //okay, that was all we could find.
|
|
}
|
|
MSG_WriteByte(&host_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString(&host_client->netchan.message, va("soundlist %i 0\n", svs.spawncount));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
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
|
|
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 );
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
==================
|
|
SV_NextDownload_f
|
|
==================
|
|
*/
|
|
void SV_NextDownload_f (void)
|
|
{
|
|
qbyte buffer[1024];
|
|
int r;
|
|
int percent;
|
|
int size;
|
|
|
|
if (!host_client->download)
|
|
return;
|
|
|
|
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
|
|
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);
|
|
ClientReliableWrite_Begin (host_client, host_client->isq2client?svcq2_download:svc_download, 4);
|
|
ClientReliableWrite_Short (host_client, -1);
|
|
ClientReliableWrite_Byte (host_client, 0);
|
|
return;
|
|
}
|
|
|
|
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);
|
|
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);
|
|
|
|
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)
|
|
{
|
|
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;
|
|
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->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);
|
|
Con_Printf("Command was disabled\n");
|
|
SV_EndRedirect ();
|
|
}
|
|
else
|
|
{
|
|
SV_BeginRedirect (RD_CLIENT);
|
|
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;
|
|
|
|
#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;
|
|
|
|
#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.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 != 0);
|
|
|
|
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_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;
|
|
|
|
// calc ping time
|
|
if (cl->frames)
|
|
{ //split screen doesn't always have frames.
|
|
frame = &cl->frames[cl->netchan.incoming_acknowledged & UPDATE_MASK];
|
|
frame->ping_time = realtime - frame->senttime;
|
|
}
|
|
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 (host_client->spectator||sv.mvdplayback) {
|
|
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
|