4cd41b9e49
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4476 fc73d0e0-1445-4013-8a0c-d673dee63da5
7374 lines
191 KiB
C
7374 lines
191 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 "quakedef.h"
|
|
|
|
#ifndef CLIENTONLY
|
|
#include "pr_common.h"
|
|
|
|
#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 = SCVAR("cl_rollspeed", "200");
|
|
cvar_t cl_rollangle = SCVAR("cl_rollangle", "2.0");
|
|
#else
|
|
extern cvar_t cl_rollspeed;
|
|
extern cvar_t cl_rollangle;
|
|
#endif
|
|
cvar_t sv_spectalk = SCVAR("sv_spectalk", "1");
|
|
|
|
cvar_t sv_mapcheck = SCVAR("sv_mapcheck", "1");
|
|
|
|
cvar_t sv_fullredirect = CVARD("sv_fullredirect", "", "This is the ip:port to redirect players to when the server is full");
|
|
cvar_t sv_antilag = CVARFD("sv_antilag", "1", CVAR_SERVERINFO, "Attempt to backdate impacts to compensate for lag. 0=completely off. 1=mod-controlled. 2=forced, which might break certain uses of traceline.");
|
|
cvar_t sv_antilag_frac = CVARF("sv_antilag_frac", "1", CVAR_SERVERINFO);
|
|
cvar_t sv_cheatpc = CVAR("sv_cheatpc", "125");
|
|
cvar_t sv_cheatspeedchecktime = CVAR("sv_cheatspeedchecktime", "30");
|
|
cvar_t sv_playermodelchecks = CVAR("sv_playermodelchecks", "0");
|
|
cvar_t sv_ping_ignorepl = CVARD("sv_ping_ignorepl", "0", "If 1, ping times reported for players will ignore the effects of packetloss on ping times. 0 is slightly more honest, but less useful for connection diagnosis.");
|
|
cvar_t sv_protocol_nq = CVARD("sv_protocol_nq", "0", "Specifies the default protocol to use for new NQ clients. Supported values are\n0 = autodetect\n15 = vanilla\n666 = fitzquake\n999 = rmq protocol\nThe sv_bigcoords cvar forces upgrades as required.");
|
|
|
|
cvar_t sv_cmdlikercon = SCVAR("sv_cmdlikercon", "0"); //set to 1 to allow a password of username:password instead of the correct rcon password.
|
|
cvar_t cmd_allowaccess = SCVAR("cmd_allowaccess", "0"); //set to 1 to allow cmd to execute console commands on the server.
|
|
cvar_t cmd_gamecodelevel = SCVAR("cmd_gamecodelevel", STRINGIFY(RESTRICT_LOCAL)); //execution level which gamecode is told about (for unrecognised commands)
|
|
|
|
cvar_t sv_pure = CVARFD("sv_pure", "", CVAR_SERVERINFO, "The most evil cvar in the world, many clients will ignore this.\n0=standard quake rules.\n1=clients should prefer files within packages present on the server.\n2=clients should use *only* files within packages present on the server.\nDue to quake 1.01/1.06 differences, a setting of 2 only works in total conversions.");
|
|
cvar_t sv_nqplayerphysics = CVARAD("sv_nqplayerphysics", "0", "sv_nomsec", "Disable player prediction and run NQ-style player physics instead. This can be used for compatibility with mods that expect exact behaviour.");
|
|
cvar_t sv_edgefriction = CVARAF("sv_edgefriction", "2",
|
|
"edgefriction", 0);
|
|
|
|
cvar_t sv_brokenmovetypes = CVARD("sv_brokenmovetypes", "0", "Emulate vanilla quakeworld by forcing MOVETYPE_WALK on all players. Shouldn't be used for any games other than QuakeWorld.");
|
|
|
|
cvar_t sv_chatfilter = CVAR("sv_chatfilter", "0");
|
|
|
|
cvar_t sv_floodprotect = CVAR("sv_floodprotect", "1");
|
|
cvar_t sv_floodprotect_messages = CVAR("sv_floodprotect_messages", "4");
|
|
cvar_t sv_floodprotect_interval = CVAR("sv_floodprotect_interval", "4");
|
|
cvar_t sv_floodprotect_silencetime = CVAR("sv_floodprotect_silencetime", "10");
|
|
cvar_t sv_floodprotect_suicide = CVAR("sv_floodprotect_suicide", "1");
|
|
cvar_t sv_floodprotect_sendmessage = CVARAF("sv_floodprotect_sendmessage", "",
|
|
"floodprotmsg", 0);
|
|
|
|
cvar_t votelevel = SCVAR("votelevel", "0");
|
|
cvar_t voteminimum = SCVAR("voteminimum", "4");
|
|
cvar_t votepercent = SCVAR("votepercent", "-1");
|
|
cvar_t votetime = SCVAR("votetime", "10");
|
|
|
|
cvar_t pr_allowbutton1 = SCVARF("pr_allowbutton1", "1", CVAR_LATCH);
|
|
extern cvar_t sv_minping;
|
|
|
|
|
|
extern cvar_t pm_bunnyspeedcap;
|
|
extern cvar_t pm_ktjump;
|
|
extern cvar_t pm_slidefix;
|
|
extern cvar_t pm_slidyslopes;
|
|
extern cvar_t pm_airstep;
|
|
extern cvar_t pm_walljump;
|
|
cvar_t sv_pushplayers = SCVAR("sv_pushplayers", "0");
|
|
|
|
//yes, realip cvars need to be fully initialised or realip will be disabled
|
|
cvar_t sv_getrealip = CVARD("sv_getrealip", "0", "Attempt to obtain a more reliable IP for clients, rather than just their proxy.");
|
|
cvar_t sv_realip_kick = SCVAR("sv_realip_kick", "0");
|
|
cvar_t sv_realiphostname_ipv4 = CVARD("sv_realiphostname_ipv4", "", "This is the server's public ip:port. This is needed for realip to work when the autodetected/local ip is not globally routable");
|
|
cvar_t sv_realiphostname_ipv6 = CVARD("sv_realiphostname_ipv6", "", "This is the server's public ip:port. This is needed for realip to work when the autodetected/local ip is not globally routable");
|
|
cvar_t sv_realip_timeout = SCVAR("sv_realip_timeout", "10");
|
|
|
|
#ifdef VOICECHAT
|
|
cvar_t sv_voip = CVARD("sv_voip", "1", "Enable reception of voice packets.");
|
|
cvar_t sv_voip_record = CVARD("sv_voip_record", "0", "Record voicechat into mvds. Requires player support. 0=noone, 1=everyone, 2=spectators only");
|
|
cvar_t sv_voip_echo = CVARD("sv_voip_echo", "0", "Echo voice packets back to their sender, a debug/test setting.");
|
|
#endif
|
|
|
|
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 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.
|
|
============================================================
|
|
*/
|
|
|
|
qboolean SV_CheckRealIP(client_t *client, qboolean force)
|
|
{
|
|
//returns true if they have a real ip
|
|
char *serverip;
|
|
char *msg;
|
|
|
|
if (sv_getrealip.value <= client->realip_status || sv_getrealip.value > 3)
|
|
return true;
|
|
|
|
if (client->netchan.remote_address.type == NA_LOOPBACK)
|
|
return true; //the loopback client doesn't have to pass realip checks
|
|
|
|
if (client->realip_status == 3)
|
|
return true; //we know that the ip is authentic
|
|
if (client->realip_status == 2)
|
|
{
|
|
ClientReliableWrite_Begin(client, svc_print, 256);
|
|
ClientReliableWrite_Byte(client, PRINT_HIGH);
|
|
ClientReliableWrite_String(client, "Couldn't verify your real ip\n");
|
|
return true; //client doesn't support certainty.
|
|
}
|
|
if (client->realip_status == -1)
|
|
return true; //this client timed out.
|
|
|
|
if (realtime - client->connection_started > sv_realip_timeout.value)
|
|
{
|
|
ClientReliableWrite_Begin(client, svc_print, 256);
|
|
ClientReliableWrite_Byte(client, PRINT_HIGH);
|
|
if (client->realip_status > 0)
|
|
ClientReliableWrite_String(client, "Couldn't verify your real ip\n");
|
|
else
|
|
ClientReliableWrite_String(client, "Couldn't determine your real ip\n");
|
|
if (sv_realip_kick.value > host_client->realip_status)
|
|
{
|
|
client->drop = true;
|
|
return false;
|
|
}
|
|
if (!client->realip_status)
|
|
client->realip_status = -1;
|
|
return true;
|
|
}
|
|
|
|
|
|
if (client->realip_status == 1)
|
|
{
|
|
msg = va("\xff\xff\xff\xff%c %i", A2A_PING, client->realip_ping);
|
|
NET_SendPacket(NS_SERVER, strlen(msg), msg, &client->realip);
|
|
}
|
|
else
|
|
{
|
|
if (client->netchan.remote_address.type == NA_IPV6)
|
|
{
|
|
serverip = sv_realiphostname_ipv6.string;
|
|
// serverip = NET_AdrToString (net_local_sv_ip6adr);
|
|
}
|
|
else
|
|
{
|
|
serverip = sv_realiphostname_ipv4.string;
|
|
// serverip = NET_AdrToString (net_local_sv_ipadr);
|
|
}
|
|
|
|
if (!*serverip)
|
|
{
|
|
Con_Printf("realip not fully configured\n");
|
|
client->realip_status = -1;
|
|
return true;
|
|
}
|
|
|
|
ClientReliableWrite_Begin(client, svc_stufftext, 256);
|
|
ClientReliableWrite_String(client, va("packet %s \"realip %i %i\"\n", serverip, (int)(client-svs.clients), client->realip_num));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
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;
|
|
|
|
host_client->prespawn_stage = PRESPAWN_INVALID;
|
|
host_client->prespawn_idx = 0;
|
|
|
|
if (host_client->state == cs_spawned)
|
|
return;
|
|
|
|
if (host_client->redirect)
|
|
{
|
|
char *msg = va("connect \"%s\"\n", sv_fullredirect.string);
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(msg));
|
|
ClientReliableWrite_String (host_client, msg);
|
|
return;
|
|
}
|
|
|
|
/* 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] || !strcmp(gamedir, "fte"))
|
|
{
|
|
if (ISQWCLIENT(host_client))
|
|
gamedir = "qw";
|
|
else
|
|
gamedir = "";
|
|
}
|
|
|
|
//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 (svs.netprim.coordsize > 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;
|
|
}
|
|
|
|
ClientReliableCheckBlock(host_client, 800); //okay, so it might be longer, but I'm too lazy to work out the real size.
|
|
|
|
// send the serverdata
|
|
ClientReliableWrite_Byte (host_client, ISQ2CLIENT(host_client)?svcq2_serverdata:svc_serverdata);
|
|
#ifdef PROTOCOL_VERSION_FTE
|
|
if (host_client->fteprotocolextensions)//let the client know
|
|
{
|
|
ClientReliableWrite_Long (host_client, PROTOCOL_VERSION_FTE);
|
|
if (svs.netprim.coordsize == 2) //we're not using float orgs on this level.
|
|
ClientReliableWrite_Long (host_client, host_client->fteprotocolextensions&~PEXT_FLOATCOORDS);
|
|
else
|
|
ClientReliableWrite_Long (host_client, host_client->fteprotocolextensions|PEXT_FLOATCOORDS);
|
|
}
|
|
if (host_client->fteprotocolextensions2)//let the client know
|
|
{
|
|
ClientReliableWrite_Long (host_client, PROTOCOL_VERSION_FTE2);
|
|
ClientReliableWrite_Long (host_client, host_client->fteprotocolextensions2);
|
|
}
|
|
#endif
|
|
ClientReliableWrite_Long (host_client, ISQ2CLIENT(host_client)?PROTOCOL_VERSION_Q2:PROTOCOL_VERSION_QW);
|
|
ClientReliableWrite_Long (host_client, svs.spawncount);
|
|
if (ISQ2CLIENT(host_client))
|
|
ClientReliableWrite_Byte (host_client, 0);
|
|
ClientReliableWrite_String (host_client, gamedir);
|
|
|
|
if (host_client->fteprotocolextensions2 & PEXT2_MAXPLAYERS)
|
|
{
|
|
/*is this a sane way to do it? or should we split the spectator thing off entirely?*/
|
|
ClientReliableWrite_Byte (host_client, sv.allocated_client_slots);
|
|
splitnum = 0;
|
|
for (split = host_client, splitnum = 0; split; split = split->controlled)
|
|
splitnum++;
|
|
ClientReliableWrite_Byte (host_client, (host_client->spectator?128:0) | splitnum); //read each player's userinfo to see if its a spectator or not. this hint is merely a cheat.
|
|
for (split = host_client; split; split = split->controlled)
|
|
{
|
|
playernum = NUM_FOR_EDICT(svprogfuncs, split->edict)-1;
|
|
if (sv.state == ss_cinematic)
|
|
playernum = -1;
|
|
ClientReliableWrite_Byte (host_client, playernum);
|
|
|
|
split->state = cs_connected;
|
|
split->connection_started = realtime;
|
|
#ifdef SVRANKING
|
|
split->stats_started = realtime;
|
|
#endif
|
|
splitnum++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
splitnum = 0;
|
|
for (split = host_client; split; split = split->controlled)
|
|
{
|
|
switch(svs.gametype)
|
|
{
|
|
#ifdef HLSERVER
|
|
case GT_HALFLIFE:
|
|
playernum = split - svs.clients;
|
|
break;
|
|
#endif
|
|
#ifdef Q2SERVER
|
|
case GT_QUAKE2:
|
|
playernum = Q2NUM_FOR_EDICT(split->q2edict)-1;
|
|
break;
|
|
#endif
|
|
default:
|
|
playernum = NUM_FOR_EDICT(svprogfuncs, split->edict)-1;
|
|
}
|
|
#ifdef SERVER_DEMO_PLAYBACK
|
|
if (sv.demostate)
|
|
{
|
|
playernum = (MAX_CLIENTS-1-splitnum)|128;
|
|
}
|
|
else
|
|
#endif
|
|
if (split->spectator)
|
|
playernum |= 128;
|
|
|
|
if (sv.state == ss_cinematic)
|
|
playernum = -1;
|
|
|
|
if (ISQ2CLIENT(host_client))
|
|
ClientReliableWrite_Short (host_client, playernum);
|
|
else
|
|
ClientReliableWrite_Byte (host_client, playernum);
|
|
|
|
split->state = cs_connected;
|
|
split->connection_started = realtime;
|
|
#ifdef SVRANKING
|
|
split->stats_started = realtime;
|
|
#endif
|
|
splitnum++;
|
|
}
|
|
|
|
if (host_client->fteprotocolextensions & PEXT_SPLITSCREEN)
|
|
ClientReliableWrite_Byte (host_client, 128);
|
|
}
|
|
|
|
// send full levelname
|
|
#ifdef SERVER_DEMO_PLAYBACK
|
|
if (sv.demostatevalid)
|
|
ClientReliableWrite_String (host_client, sv.demfullmapname);
|
|
else
|
|
#endif
|
|
ClientReliableWrite_String (host_client, sv.mapname);
|
|
|
|
//
|
|
// game server
|
|
//
|
|
#ifdef Q2SERVER
|
|
if (ISQ2CLIENT(host_client))
|
|
{
|
|
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
|
|
ClientReliableWrite_Byte (host_client, svcq2_stufftext);
|
|
ClientReliableWrite_String (host_client, va("cmd configstrings %i 0\n",svs.spawncount) );
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
// send the movevars
|
|
ClientReliableWrite_Float(host_client, movevars.gravity);
|
|
ClientReliableWrite_Float(host_client, movevars.stopspeed);
|
|
ClientReliableWrite_Float(host_client, movevars.maxspeed);
|
|
ClientReliableWrite_Float(host_client, movevars.spectatormaxspeed);
|
|
ClientReliableWrite_Float(host_client, movevars.accelerate);
|
|
ClientReliableWrite_Float(host_client, movevars.airaccelerate);
|
|
ClientReliableWrite_Float(host_client, movevars.wateraccelerate);
|
|
ClientReliableWrite_Float(host_client, movevars.friction);
|
|
ClientReliableWrite_Float(host_client, movevars.waterfriction);
|
|
ClientReliableWrite_Float(host_client, movevars.entgravity);
|
|
|
|
host_client->csqcactive = false;
|
|
|
|
host_client->realip_num = rand()+(host_client->challenge<<16);
|
|
SV_CheckRealIP(host_client, false);
|
|
|
|
SV_LogPlayer(host_client, "new (QW)");
|
|
|
|
host_client->prespawn_stage = PRESPAWN_SERVERINFO;
|
|
host_client->prespawn_idx = 0;
|
|
}
|
|
|
|
void SVNQ_New_f (void)
|
|
{
|
|
extern cvar_t coop;
|
|
char message[2048];
|
|
int i;
|
|
int maxplayers = 0;
|
|
int op;
|
|
unsigned int protext1 = 0, protext2 = 0, protmain = 0, protfl = 0;
|
|
char *protoname;
|
|
|
|
host_client->prespawn_stage = PRESPAWN_INVALID;
|
|
host_client->prespawn_idx = 0;
|
|
|
|
host_client->send_message = true;
|
|
if (host_client->redirect)
|
|
{
|
|
char *msg = va("connect \"%s\"\n", sv_fullredirect.string);
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(msg));
|
|
ClientReliableWrite_String (host_client, msg);
|
|
return;
|
|
}
|
|
|
|
if (!host_client->pextknown)
|
|
{
|
|
char *msg = va("cmd pext\n");
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(msg));
|
|
ClientReliableWrite_String (host_client, msg);
|
|
return;
|
|
}
|
|
|
|
|
|
Z_Free(host_client->csqcentversions);
|
|
host_client->csqcentversions = NULL;
|
|
Z_Free(host_client->csqcentsequence);
|
|
host_client->csqcentsequence = NULL;
|
|
host_client->csqcactive = false;
|
|
|
|
protext1 = host_client->fteprotocolextensions;
|
|
protext2 = host_client->fteprotocolextensions2;
|
|
protmain = NQ_PROTOCOL_VERSION;
|
|
protfl = 0;
|
|
//force floatcoords as required.
|
|
if (sv.nqdatagram.prim.coordsize >= 4)
|
|
protext1 |= PEXT_FLOATCOORDS;
|
|
else
|
|
protext1 &= ~PEXT_FLOATCOORDS;
|
|
|
|
op = host_client->protocol;
|
|
switch(host_client->protocol)
|
|
{
|
|
#ifdef NQPROT
|
|
case SCP_NETQUAKE:
|
|
case SCP_PROQUAKE:
|
|
case SCP_FITZ666:
|
|
SV_LogPlayer(host_client, "new (NQ)");
|
|
if (sv.nqdatagram.prim.anglesize != 1 || sv.nqdatagram.prim.coordsize != 2 || host_client->protocol == SCP_FITZ666)
|
|
{
|
|
protfl =
|
|
((sv.nqdatagram.prim.coordsize==4)?RMQFL_FLOATCOORD:0) |
|
|
((sv.nqdatagram.prim.anglesize==2)?RMQFL_SHORTANGLE:0);
|
|
host_client->protocol = SCP_FITZ666; /*mneh, close enough, the rmq stuff is just modifiers*/
|
|
|
|
if (protfl)
|
|
{
|
|
protext1 &= ~PEXT_FLOATCOORDS; //never report floatcoords when using rmq protocol, as the base protocol allows us to be more specific anyway.
|
|
protmain = RMQ_PROTOCOL_VERSION;
|
|
protoname = "RMQ";
|
|
}
|
|
else
|
|
{
|
|
protmain = FITZ_PROTOCOL_VERSION;
|
|
protoname = "666";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
host_client->protocol = (host_client->protocol==SCP_PROQUAKE)?SCP_PROQUAKE:SCP_NETQUAKE; //identical other than the client->server angles
|
|
protmain = NQ_PROTOCOL_VERSION;
|
|
protoname = "NQ";
|
|
}
|
|
maxplayers = 16;
|
|
break;
|
|
case SCP_DARKPLACES6:
|
|
SV_LogPlayer(host_client, "new (DP6)");
|
|
protmain = DP6_PROTOCOL_VERSION;
|
|
protext1 &= ~PEXT_FLOATCOORDS; //always enabled, try not to break things
|
|
maxplayers = 255;
|
|
protoname = "DPP6";
|
|
break;
|
|
case SCP_DARKPLACES7:
|
|
SV_LogPlayer(host_client, "new (DP7)");
|
|
protmain = DP7_PROTOCOL_VERSION;
|
|
protext1 &= ~PEXT_FLOATCOORDS; //always enabled, try not to break things
|
|
maxplayers = 255;
|
|
protoname = "DPP7";
|
|
break;
|
|
#endif
|
|
default:
|
|
host_client->drop = true;
|
|
protoname = "?""?""?";
|
|
break;
|
|
}
|
|
|
|
MSG_WriteByte (&host_client->netchan.message, svc_print);
|
|
Q_snprintfz (message, sizeof(message), "%c\n%s %s%s%s%s server\n", 2, version_string(), (protext2&PEXT2_REPLACEMENTDELTAS)?"FTE":"", protoname, (protext1||(protext2&~(PEXT2_REPLACEMENTDELTAS|PEXT2_VOICECHAT)))?"+":"", (protext2&PEXT2_VOICECHAT)?"V":"");
|
|
MSG_WriteString (&host_client->netchan.message,message);
|
|
|
|
if (host_client->protocol == SCP_DARKPLACES6 || host_client->protocol == SCP_DARKPLACES7)
|
|
{
|
|
extern cvar_t allow_download;
|
|
char *f;
|
|
|
|
if (allow_download.value)
|
|
{
|
|
MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&host_client->netchan.message, "cl_serverextension_download 1\n");
|
|
}
|
|
|
|
f = COM_LoadTempFile("csprogs.dat");
|
|
if (f)
|
|
{
|
|
MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&host_client->netchan.message, va("csqc_progname %s\n", "csprogs.dat"));
|
|
MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&host_client->netchan.message, va("csqc_progsize %i\n", com_filesize));
|
|
MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&host_client->netchan.message, va("csqc_progcrc %i\n", QCRC_Block(f, com_filesize)));
|
|
|
|
MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&host_client->netchan.message, "cmd enablecsqc\n");
|
|
}
|
|
}
|
|
|
|
MSG_WriteByte (&host_client->netchan.message, svc_serverdata);
|
|
if (protext1)
|
|
{
|
|
MSG_WriteLong (&host_client->netchan.message, PROTOCOL_VERSION_FTE);
|
|
MSG_WriteLong (&host_client->netchan.message, protext1);
|
|
}
|
|
if (protext2)
|
|
{
|
|
MSG_WriteLong (&host_client->netchan.message, PROTOCOL_VERSION_FTE2);
|
|
MSG_WriteLong (&host_client->netchan.message, protext2);
|
|
}
|
|
MSG_WriteLong (&host_client->netchan.message, protmain);
|
|
if (protmain == RMQ_PROTOCOL_VERSION)
|
|
MSG_WriteLong (&host_client->netchan.message, protfl);
|
|
MSG_WriteByte (&host_client->netchan.message, (sv.allocated_client_slots>maxplayers)?maxplayers:sv.allocated_client_slots);
|
|
|
|
if (op != host_client->protocol)
|
|
SV_ClientProtocolExtensionsChanged(host_client);
|
|
|
|
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.strings.model_precache[i] ; i++)
|
|
MSG_WriteString (&host_client->netchan.message, sv.strings.model_precache[i]);
|
|
MSG_WriteByte (&host_client->netchan.message, 0);
|
|
|
|
for (i = 1; *sv.strings.sound_precache[i] ; i++)
|
|
MSG_WriteString (&host_client->netchan.message, sv.strings.sound_precache[i]);
|
|
MSG_WriteByte (&host_client->netchan.message, 0);
|
|
|
|
// set view
|
|
MSG_WriteByte (&host_client->netchan.message, svc_setview);
|
|
MSG_WriteEntity (&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);
|
|
|
|
// host_client->sendsignon = true;
|
|
// host_client->spawned = false; // need prespawn, spawn, etc
|
|
|
|
host_client->prespawn_stage = PRESPAWN_SERVERINFO;
|
|
host_client->prespawn_idx = 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef Q2SERVER
|
|
void SVQ2_ConfigStrings_f (void)
|
|
{
|
|
int start;
|
|
char *str;
|
|
|
|
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 < host_client->netchan.message.maxsize/2
|
|
&& start < Q2MAX_CONFIGSTRINGS)
|
|
{
|
|
str = sv.strings.configstring[start];
|
|
if (*str)
|
|
{
|
|
MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
|
|
MSG_WriteShort (&host_client->netchan.message, start);
|
|
MSG_WriteString (&host_client->netchan.message, str);
|
|
}
|
|
/*
|
|
//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);
|
|
}
|
|
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)
|
|
{
|
|
extern int map_checksum;
|
|
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 < host_client->netchan.message.maxsize/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
|
|
|
|
/*
|
|
==================
|
|
SV_Soundlist_f
|
|
==================
|
|
*/
|
|
void SVQW_Soundlist_f (void)
|
|
{
|
|
if (host_client->prespawn_stage == PRESPAWN_SOUNDLIST)
|
|
host_client->prespawn_idx &= ~0x80000000;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Modellist_f
|
|
==================
|
|
*/
|
|
void SVQW_Modellist_f (void)
|
|
{
|
|
if ((host_client->prespawn_stage == PRESPAWN_MODELLIST) || (host_client->prespawn_stage == PRESPAWN_VWEPMODELLIST))
|
|
host_client->prespawn_idx &= ~0x80000000;
|
|
}
|
|
|
|
void SV_SendClientPrespawnInfo(client_t *client)
|
|
{
|
|
qboolean started;
|
|
int i;
|
|
entity_state_t *state;
|
|
edict_t *ent;
|
|
svcustomtents_t *ctent;
|
|
//much of this function is written to fill packets enough to overflow them (assuming max packet sizes are large enough), but some bits are lazy and just backbuffer as needed.
|
|
//FIXME: have per-stage indicies, to allow returning to a previous stage when new precaches or whatever get added
|
|
|
|
if (client->num_backbuf || client->prespawn_stage == PRESPAWN_DONE)
|
|
{
|
|
//don't spam too much.
|
|
return;
|
|
}
|
|
|
|
if (client->prespawn_stage == PRESPAWN_SERVERINFO)
|
|
{
|
|
char buffer[1024];
|
|
|
|
while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))
|
|
{
|
|
if (client->prespawn_idx == 0)
|
|
{
|
|
if (!ISNQCLIENT(client) || (client->fteprotocolextensions2 & PEXT2_PREDINFO))
|
|
{ //nq does not normally get serverinfo sent to it.
|
|
ClientReliableWrite_Begin(client, svc_stufftext, 20 + strlen(svs.info));
|
|
ClientReliableWrite_String (client, va("fullserverinfo \"%s\"\n", svs.info) );
|
|
}
|
|
}
|
|
else if (client->prespawn_idx == 1)
|
|
{
|
|
int track = 0;
|
|
|
|
if (progstype == PROG_H2)
|
|
track = sv.h2cdtrack; //hexen2 has a special hack
|
|
else if (svprogfuncs)
|
|
track = ((edict_t*)sv.world.edicts)->v->sounds;
|
|
|
|
ClientReliableWrite_Begin(client, svc_cdtrack, 2);
|
|
ClientReliableWrite_Byte (client, track);
|
|
if (ISNQCLIENT(client))
|
|
ClientReliableWrite_Byte (client, track);
|
|
}
|
|
else if (client->prespawn_idx == 2)
|
|
{
|
|
FS_GetPackNames(buffer, sizeof(buffer), 2, true); /*retain extensions, or we'd have to assume pk3*/
|
|
ClientReliableWrite_Begin(client, svc_stufftext, 1+11+strlen(buffer)+1+1);
|
|
ClientReliableWrite_SZ(client, "//paknames ", 11);
|
|
ClientReliableWrite_SZ(client, buffer, strlen(buffer));
|
|
ClientReliableWrite_String(client, "\n");
|
|
}
|
|
else if (client->prespawn_idx == 3)
|
|
{
|
|
FS_GetPackHashes(buffer, sizeof(buffer), false);
|
|
ClientReliableWrite_Begin(client, svc_stufftext, 1+7+strlen(buffer)+1+1);
|
|
ClientReliableWrite_SZ(client, "//paks ", 7);
|
|
ClientReliableWrite_SZ(client, buffer, strlen(buffer));
|
|
ClientReliableWrite_String(client, "\n");
|
|
}
|
|
else if (client->prespawn_idx == 4)
|
|
{
|
|
ClientReliableWrite_Begin(client, svc_setpause, 2);
|
|
ClientReliableWrite_Byte (client, sv.paused);
|
|
}
|
|
else
|
|
{
|
|
client->prespawn_stage++;
|
|
client->prespawn_idx = 0;
|
|
break;
|
|
}
|
|
client->prespawn_idx++;
|
|
}
|
|
}
|
|
|
|
if (client->prespawn_stage == PRESPAWN_SOUNDLIST)
|
|
{
|
|
if (!ISQWCLIENT(client))
|
|
client->prespawn_stage++;
|
|
else
|
|
{
|
|
int maxclientsupportedsounds = 256;
|
|
#ifdef PEXT_SOUNDDBL
|
|
if (client->fteprotocolextensions & PEXT_SOUNDDBL)
|
|
maxclientsupportedsounds = MAX_SOUNDS;
|
|
#endif
|
|
started = false;
|
|
|
|
//allows stalling for the soundlist command, for compat.
|
|
if (client->prespawn_idx & 0x80000000)
|
|
return;
|
|
|
|
while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))
|
|
{
|
|
if (!started)
|
|
{
|
|
started = true;
|
|
#ifdef PEXT_SOUNDDBL
|
|
if (client->prespawn_idx > 255)
|
|
{
|
|
MSG_WriteByte (&client->netchan.message, svcfte_soundlistshort);
|
|
MSG_WriteShort (&client->netchan.message, client->prespawn_idx);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
MSG_WriteByte (&client->netchan.message, svc_soundlist);
|
|
MSG_WriteByte (&client->netchan.message, client->prespawn_idx);
|
|
}
|
|
}
|
|
client->prespawn_idx++;
|
|
|
|
if (client->prespawn_idx >= maxclientsupportedsounds || !*sv.strings.sound_precache[client->prespawn_idx])
|
|
{
|
|
//write final-end-of-list
|
|
MSG_WriteByte (&client->netchan.message, 0);
|
|
MSG_WriteByte (&client->netchan.message, 0);
|
|
started = 0;
|
|
client->prespawn_stage++;
|
|
client->prespawn_idx = 0;
|
|
break;
|
|
}
|
|
else
|
|
MSG_WriteString (&client->netchan.message, sv.strings.sound_precache[client->prespawn_idx]);
|
|
}
|
|
if (started)
|
|
{
|
|
//write end-of-packet
|
|
MSG_WriteByte (&client->netchan.message, 0);
|
|
MSG_WriteByte (&client->netchan.message, (client->prespawn_idx&0xff)?client->prespawn_idx:0xff);
|
|
|
|
if (!(client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS))
|
|
client->prespawn_idx |= 0x80000000;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (client->prespawn_stage == PRESPAWN_VWEPMODELLIST)
|
|
{
|
|
//no indicies. the protocol can't cope with them.
|
|
if (client->zquake_extensions & Z_EXT_VWEP)
|
|
{
|
|
char mname[MAX_QPATH];
|
|
char vweaplist[1024] = "//vwep";
|
|
|
|
for (i = 0; sv.strings.vw_model_precache[i]; i++)
|
|
{
|
|
//grab the model name... without a progs/ prefix if it has one
|
|
if (!strncmp(sv.strings.vw_model_precache[i], "progs/", 6))
|
|
Q_strncpy(mname, sv.strings.vw_model_precache[i]+6, sizeof(mname));
|
|
else
|
|
Q_strncpy(mname, sv.strings.vw_model_precache[i], sizeof(mname));
|
|
|
|
//strip .mdl extensions, for compat with ezquake
|
|
if (!strcmp(COM_FileExtension(mname), "mdl"))
|
|
COM_StripExtension(mname, mname, sizeof(mname));
|
|
|
|
//add it to the vweap command, taking care of any remaining spaces in names.
|
|
if (strchr(mname, ' ') || !*mname)
|
|
Q_strncatz(vweaplist, va(" \"%s\"", mname), sizeof(vweaplist));
|
|
else
|
|
Q_strncatz(vweaplist, va(" %s", mname), sizeof(vweaplist));
|
|
}
|
|
|
|
if (strlen(vweaplist) <= sizeof(vweaplist)-2)
|
|
{
|
|
Q_strncatz(vweaplist, "\n", sizeof(vweaplist));
|
|
|
|
ClientReliableWrite_Begin(client, svc_stufftext, 2+strlen(vweaplist));
|
|
ClientReliableWrite_String(client, vweaplist);
|
|
}
|
|
}
|
|
client->prespawn_stage++;
|
|
}
|
|
|
|
if (client->prespawn_stage == PRESPAWN_MODELLIST)
|
|
{
|
|
if (!ISQWCLIENT(client))
|
|
client->prespawn_stage++;
|
|
else
|
|
{
|
|
started = false;
|
|
|
|
//allows stalling for the soundlist command, for compat.
|
|
if (client->prespawn_idx & 0x80000000)
|
|
return;
|
|
|
|
while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))
|
|
{
|
|
if (!started)
|
|
{
|
|
started = true;
|
|
#ifdef PEXT_SOUNDDBL
|
|
if (client->prespawn_idx > 255)
|
|
{
|
|
MSG_WriteByte (&client->netchan.message, svcfte_modellistshort);
|
|
MSG_WriteShort (&client->netchan.message, client->prespawn_idx);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
MSG_WriteByte (&client->netchan.message, svc_modellist);
|
|
MSG_WriteByte (&client->netchan.message, client->prespawn_idx);
|
|
}
|
|
}
|
|
client->prespawn_idx++;
|
|
|
|
if (client->prespawn_idx >= client->maxmodels || !sv.strings.model_precache[client->prespawn_idx])
|
|
{
|
|
//write final-end-of-list
|
|
MSG_WriteByte (&client->netchan.message, 0);
|
|
MSG_WriteByte (&client->netchan.message, 0);
|
|
started = 0;
|
|
|
|
client->prespawn_stage++;
|
|
client->prespawn_idx = 0;
|
|
break;
|
|
}
|
|
else
|
|
MSG_WriteString (&client->netchan.message, sv.strings.model_precache[client->prespawn_idx]);
|
|
}
|
|
if (started)
|
|
{
|
|
//write end-of-packet
|
|
MSG_WriteByte (&client->netchan.message, 0);
|
|
MSG_WriteByte (&client->netchan.message, (client->prespawn_idx&0xff)?client->prespawn_idx:0xff);
|
|
|
|
if (!(client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS))
|
|
client->prespawn_idx |= 0x80000000;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (client->prespawn_stage == PRESPAWN_MAPCHECK)
|
|
{
|
|
//can't progress beyond this as we're waiting for the client.
|
|
// host_client->prespawn_idx = client->prespawn_idx;
|
|
return;
|
|
}
|
|
|
|
if (client->prespawn_stage == PRESPAWN_PARTICLES)
|
|
{
|
|
if (!(client->fteprotocolextensions & PEXT_CSQC))
|
|
client->prespawn_idx = MAX_SSPARTICLESPRE;
|
|
while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))
|
|
{
|
|
if (client->prespawn_idx >= MAX_SSPARTICLESPRE)
|
|
{
|
|
client->prespawn_stage++;
|
|
client->prespawn_idx = 0;
|
|
break;
|
|
}
|
|
|
|
if (*sv.strings.particle_precache[client->prespawn_idx])
|
|
{
|
|
ClientReliableWrite_Begin (client, ISNQCLIENT(client)?svcdp_precache:svcfte_precache, 4 + strlen(sv.strings.particle_precache[client->prespawn_idx]));
|
|
ClientReliableWrite_Short (client, client->prespawn_idx | PC_PARTICLE);
|
|
ClientReliableWrite_String (client, sv.strings.particle_precache[client->prespawn_idx]);
|
|
}
|
|
client->prespawn_idx++;
|
|
}
|
|
}
|
|
|
|
if (client->prespawn_stage == PRESPAWN_CUSTOMTENTS)
|
|
{
|
|
while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))
|
|
{
|
|
if (client->prespawn_idx >= 255)
|
|
{
|
|
client->prespawn_stage++;
|
|
client->prespawn_idx = 0;
|
|
break;
|
|
}
|
|
|
|
ctent = &sv.customtents[client->prespawn_idx];
|
|
|
|
if (*ctent->particleeffecttype)
|
|
{
|
|
if (client->fteprotocolextensions & PEXT_CUSTOMTEMPEFFECTS)
|
|
{
|
|
MSG_WriteByte(&client->netchan.message, svcfte_customtempent);
|
|
MSG_WriteByte(&client->netchan.message, 255);
|
|
MSG_WriteByte(&client->netchan.message, client->prespawn_idx);
|
|
MSG_WriteByte(&client->netchan.message, ctent->netstyle);
|
|
MSG_WriteString(&client->netchan.message, ctent->particleeffecttype);
|
|
if (ctent->netstyle & CTE_STAINS)
|
|
{
|
|
MSG_WriteChar(&client->netchan.message, ctent->stain[0]);
|
|
MSG_WriteChar(&client->netchan.message, ctent->stain[0]);
|
|
MSG_WriteChar(&client->netchan.message, ctent->stain[0]);
|
|
MSG_WriteByte(&client->netchan.message, ctent->radius);
|
|
}
|
|
if (ctent->netstyle & CTE_GLOWS)
|
|
{
|
|
MSG_WriteByte(&client->netchan.message, ctent->dlightrgb[0]);
|
|
MSG_WriteByte(&client->netchan.message, ctent->dlightrgb[1]);
|
|
MSG_WriteByte(&client->netchan.message, ctent->dlightrgb[2]);
|
|
MSG_WriteByte(&client->netchan.message, ctent->dlightradius);
|
|
MSG_WriteByte(&client->netchan.message, ctent->dlighttime);
|
|
}
|
|
}
|
|
}
|
|
client->prespawn_idx++;
|
|
}
|
|
}
|
|
|
|
if (client->prespawn_stage == PRESPAWN_SIGNON_BUF)
|
|
{
|
|
while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))
|
|
{
|
|
if (client->prespawn_idx >= sv.num_signon_buffers)
|
|
{
|
|
client->prespawn_stage++;
|
|
client->prespawn_idx = 0;
|
|
break;
|
|
}
|
|
|
|
if (client->netchan.message.cursize+sv.signon_buffer_size[client->prespawn_idx]+30 < client->netchan.message.maxsize)
|
|
{
|
|
SZ_Write (&client->netchan.message,
|
|
sv.signon_buffers[client->prespawn_idx],
|
|
sv.signon_buffer_size[client->prespawn_idx]);
|
|
}
|
|
else
|
|
break;
|
|
client->prespawn_idx++;
|
|
}
|
|
}
|
|
|
|
if (client->prespawn_stage == PRESPAWN_SPAWNSTATIC)
|
|
{
|
|
while (client->netchan.message.cursize < (client->netchan.message.maxsize/2)) //static entities
|
|
{
|
|
if (client->prespawn_idx >= sv.num_static_entities)
|
|
{
|
|
client->prespawn_stage++;
|
|
client->prespawn_idx = 0;
|
|
break;
|
|
}
|
|
|
|
state = &sv_staticentities[client->prespawn_idx];
|
|
client->prespawn_idx++;
|
|
|
|
if (client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)
|
|
{
|
|
MSG_WriteByte(&client->netchan.message, svcfte_spawnstatic2);
|
|
SVFTE_EmitBaseline(state, false, &client->netchan.message);
|
|
continue;
|
|
}
|
|
if (client->fteprotocolextensions & PEXT_SPAWNSTATIC2)
|
|
{
|
|
/*if it uses some new feature, use the updated spawnstatic*/
|
|
if (state->hexen2flags || state->trans || state->modelindex >= 256 || state->frame > 255 || state->scale || state->abslight)
|
|
{
|
|
MSG_WriteByte(&client->netchan.message, svcfte_spawnstatic2);
|
|
SVQW_WriteDelta(&nullentitystate, state, &client->netchan.message, true, client->fteprotocolextensions);
|
|
continue;
|
|
}
|
|
}
|
|
/*couldn't use protocol extensions?
|
|
use the fallback, unless the model is invalid as that's silly*/
|
|
if (state->modelindex < 256)
|
|
{
|
|
MSG_WriteByte(&client->netchan.message, svc_spawnstatic);
|
|
|
|
MSG_WriteByte (&client->netchan.message, state->modelindex);
|
|
|
|
MSG_WriteByte (&client->netchan.message, state->frame);
|
|
MSG_WriteByte (&client->netchan.message, (int)state->colormap);
|
|
MSG_WriteByte (&client->netchan.message, (int)state->skinnum);
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
MSG_WriteCoord(&client->netchan.message, state->origin[i]);
|
|
MSG_WriteAngle(&client->netchan.message, state->angles[i]);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (client->prespawn_stage == PRESPAWN_BASELINES)
|
|
{
|
|
while (client->netchan.message.cursize < (client->netchan.message.maxsize/2)) //baselines
|
|
{
|
|
if (client->prespawn_idx >= sv.world.num_edicts)
|
|
{
|
|
client->prespawn_stage++;
|
|
client->prespawn_idx = 0;
|
|
break;
|
|
}
|
|
|
|
ent = EDICT_NUM(svprogfuncs, client->prespawn_idx);
|
|
|
|
if (!ent)
|
|
state = &nullentitystate;
|
|
else
|
|
state = &ent->baseline;
|
|
|
|
if (!state->number || !state->modelindex)
|
|
{ //ent doesn't have a baseline
|
|
client->prespawn_idx++;
|
|
continue;
|
|
}
|
|
|
|
if (state->number >= client->max_net_ents || state->modelindex >= client->maxmodels)
|
|
{
|
|
/*can't send this ent*/
|
|
}
|
|
else if (client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)
|
|
{
|
|
MSG_WriteByte(&client->netchan.message, svcfte_spawnbaseline2);
|
|
SVFTE_EmitBaseline(state, true, &client->netchan.message);
|
|
}
|
|
else if (client->fteprotocolextensions & PEXT_SPAWNSTATIC2)
|
|
{
|
|
MSG_WriteByte(&client->netchan.message, svcfte_spawnbaseline2);
|
|
SVQW_WriteDelta(&nullentitystate, state, &client->netchan.message, true, client->fteprotocolextensions);
|
|
}
|
|
else if (ISDPCLIENT(client) && (state->modelindex > 255 || state->frame > 255))
|
|
{
|
|
MSG_WriteByte(&client->netchan.message, svcdp_spawnbaseline2);
|
|
MSG_WriteEntity (&client->netchan.message, state->number);
|
|
|
|
MSG_WriteShort (&client->netchan.message, state->modelindex);
|
|
MSG_WriteShort (&client->netchan.message, state->frame);
|
|
MSG_WriteByte (&client->netchan.message, (int)state->colormap);
|
|
MSG_WriteByte (&client->netchan.message, (int)state->skinnum);
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
MSG_WriteCoord(&client->netchan.message, state->origin[i]);
|
|
MSG_WriteAngle(&client->netchan.message, state->angles[i]);
|
|
}
|
|
}
|
|
else if (client->protocol == SCP_FITZ666 && (state->modelindex > 255 || state->frame > 255 || state->trans != 255 || state->scale != 16))
|
|
{
|
|
int fl = 0;
|
|
if (state->modelindex > 255) fl |= FITZ_B_LARGEMODEL;
|
|
if (state->frame > 255) fl |= FITZ_B_LARGEFRAME;
|
|
if (state->trans != 255) fl |= FITZ_B_ALPHA;
|
|
if (state->scale != 16) fl |= RMQFITZ_B_SCALE;
|
|
|
|
MSG_WriteByte(&client->netchan.message, svcfitz_spawnbaseline2);
|
|
MSG_WriteEntity (&client->netchan.message, state->number);
|
|
MSG_WriteByte(&client->netchan.message, fl);
|
|
if (fl & FITZ_B_LARGEMODEL)
|
|
MSG_WriteShort (&client->netchan.message, state->modelindex);
|
|
else
|
|
MSG_WriteByte (&client->netchan.message, state->modelindex);
|
|
if (fl & FITZ_B_LARGEFRAME)
|
|
MSG_WriteShort (&client->netchan.message, state->frame);
|
|
else
|
|
MSG_WriteByte (&client->netchan.message, state->frame);
|
|
MSG_WriteByte (&client->netchan.message, state->colormap);
|
|
MSG_WriteByte (&client->netchan.message, state->skinnum);
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
MSG_WriteCoord(&client->netchan.message, state->origin[i]);
|
|
MSG_WriteAngle(&client->netchan.message, state->angles[i]);
|
|
}
|
|
if (fl & FITZ_B_ALPHA)
|
|
MSG_WriteByte (&client->netchan.message, state->trans);
|
|
if (fl & RMQFITZ_B_SCALE)
|
|
MSG_WriteByte (&client->netchan.message, state->scale);
|
|
}
|
|
else if (state->modelindex)
|
|
{
|
|
MSG_WriteByte(&client->netchan.message, svc_spawnbaseline);
|
|
MSG_WriteEntity (&client->netchan.message, state->number);
|
|
|
|
if (state->modelindex > 255)
|
|
MSG_WriteByte (&client->netchan.message, 0); //invalid modelindex. at least try to give something
|
|
else
|
|
MSG_WriteByte (&client->netchan.message, state->modelindex);
|
|
MSG_WriteByte (&client->netchan.message, state->frame&255);
|
|
MSG_WriteByte (&client->netchan.message, (int)state->colormap);
|
|
MSG_WriteByte (&client->netchan.message, (int)state->skinnum);
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
MSG_WriteCoord(&client->netchan.message, state->origin[i]);
|
|
MSG_WriteAngle(&client->netchan.message, state->angles[i]);
|
|
}
|
|
}
|
|
|
|
client->prespawn_idx++;
|
|
}
|
|
}
|
|
if (client->prespawn_stage == PRESPAWN_DONE)
|
|
{
|
|
if (!client->prespawn_idx)
|
|
{
|
|
//we'll spawn the client and then send all the updating stuff only when we know the channel is clear, by pinging the client for it.
|
|
if (ISNQCLIENT(client))
|
|
{
|
|
//effectively a cmd spawn... but also causes the client to actually send the player's name too.
|
|
ClientReliableWrite_Begin (client, svc_signonnum, 2);
|
|
ClientReliableWrite_Byte (client, 2);
|
|
}
|
|
else
|
|
{
|
|
char *cmd = va("cmd spawn %i\n",svs.spawncount);
|
|
ClientReliableWrite_Begin(client, svc_stufftext, 2+strlen(cmd));
|
|
ClientReliableWrite_String(client, cmd);
|
|
}
|
|
client->prespawn_idx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_PreSpawn_f
|
|
==================
|
|
*/
|
|
void SVQW_PreSpawn_f (void)
|
|
{
|
|
unsigned check;
|
|
|
|
if (host_client->state != cs_connected)
|
|
{
|
|
Con_Printf ("prespawn not valid -- already spawned\n");
|
|
return;
|
|
}
|
|
if (host_client->prespawn_stage != PRESPAWN_MAPCHECK)
|
|
{
|
|
Con_Printf("Wrong stage for prespawn command\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");
|
|
//FIXME: we shouldn't need the following line.
|
|
SV_New_f ();
|
|
return;
|
|
}
|
|
|
|
if (host_client->prespawn_stage == PRESPAWN_MAPCHECK)
|
|
{
|
|
check = COM_RemapMapChecksum(atoi(Cmd_Argv(3)));
|
|
|
|
// Con_DPrintf("Client check = %d\n", check);
|
|
|
|
if (sv_mapcheck.value && check != sv.world.worldmodel->checksum &&
|
|
check != COM_RemapMapChecksum(LittleLong(sv.world.worldmodel->checksum2)))
|
|
#ifdef SERVER_DEMO_PLAYBACK
|
|
if (!sv.demofile || (sv.demofile && !sv.democausesreconnect)) //demo playing causes no check. If it's the return level, check anyway to avoid that loophole.
|
|
#endif
|
|
{
|
|
char *msg;
|
|
SV_ClientTPrintf (host_client, PRINT_HIGH,
|
|
STL_MAPCHEAT,
|
|
sv.modelname, check, sv.world.worldmodel->checksum, sv.world.worldmodel->checksum2);
|
|
|
|
|
|
msg = va("\n//kickfile \"%s\"\n", sv.modelname);
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 3+strlen(msg));
|
|
ClientReliableWrite_String (host_client, msg);
|
|
SV_DropClient (host_client);
|
|
return;
|
|
}
|
|
host_client->checksum = check;
|
|
host_client->prespawn_stage = PRESPAWN_MAPCHECK+1;
|
|
host_client->prespawn_idx = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Spawn_f
|
|
==================
|
|
*/
|
|
void SVQW_Spawn_f (void)
|
|
{
|
|
int i;
|
|
client_t *client, *split;
|
|
edict_t *ent;
|
|
|
|
int secret_total, secret_found, monsters_total, monsters_found;
|
|
|
|
if (host_client->state != cs_connected)
|
|
{
|
|
Con_Printf ("Spawn not valid -- already spawned\n");
|
|
return;
|
|
}
|
|
if (host_client->prespawn_stage != PRESPAWN_DONE)
|
|
{
|
|
Con_Printf ("client sent spawn without prespawn!\n");
|
|
SV_New_f ();
|
|
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_FullClientUpdate(client, host_client);
|
|
SV_MVD_FullClientUpdate(NULL, host_client);
|
|
|
|
// send all current light styles
|
|
for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
|
|
{
|
|
#ifdef SERVER_DEMO_PLAYBACK
|
|
if (sv.democausesreconnect)
|
|
{
|
|
if (i >= MAX_STANDARDLIGHTSTYLES)
|
|
continue;
|
|
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
|
|
#endif
|
|
{
|
|
if (i >= MAX_STANDARDLIGHTSTYLES)
|
|
if (!sv.strings.lightstyles[i])
|
|
continue;
|
|
#ifdef PEXT_LIGHTSTYLECOL
|
|
if ((host_client->fteprotocolextensions & PEXT_LIGHTSTYLECOL) && sv.strings.lightstylecolours[i]!=7 && sv.strings.lightstyles[i])
|
|
{
|
|
ClientReliableWrite_Begin (host_client, svcfte_lightstylecol,
|
|
3 + (sv.strings.lightstyles[i] ? strlen(sv.strings.lightstyles[i]) : 1));
|
|
ClientReliableWrite_Byte (host_client, (char)i);
|
|
ClientReliableWrite_Char (host_client, sv.strings.lightstylecolours[i]);
|
|
ClientReliableWrite_String (host_client, sv.strings.lightstyles[i]);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
ClientReliableWrite_Begin (host_client, svc_lightstyle,
|
|
3 + (sv.strings.lightstyles[i] ? strlen(sv.strings.lightstyles[i]) : 1));
|
|
ClientReliableWrite_Byte (host_client, (char)i);
|
|
ClientReliableWrite_String (host_client, sv.strings.lightstyles[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef HLSERVER
|
|
if (svs.gametype == GT_HALFLIFE)
|
|
{
|
|
for (split = host_client; split; split = split->controlled)
|
|
{
|
|
split->entgravity = 1;
|
|
split->maxspeed = 320;
|
|
|
|
SVHL_PutClientInServer(split);
|
|
}
|
|
|
|
secret_total = 0;
|
|
secret_found = 0;
|
|
monsters_total = 0;
|
|
monsters_found = 0;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// set up the edict
|
|
for (split = host_client; split; split = split->controlled)
|
|
{
|
|
ent = split->edict;
|
|
|
|
if (split->istobeloaded) //minimal setup
|
|
{
|
|
split->entgravity = ent->xv->gravity;
|
|
split->maxspeed = ent->xv->maxspeed;
|
|
split->playerclass = ent->xv->playerclass;
|
|
}
|
|
else
|
|
{
|
|
SV_SetUpClientEdict(split, ent);
|
|
}
|
|
|
|
//
|
|
// force stats to be updated
|
|
//
|
|
memset (split->statsi, 0, sizeof(split->statsi));
|
|
memset (split->statsf, 0, sizeof(split->statsf));
|
|
memset (split->statss, 0, sizeof(split->statss));
|
|
}
|
|
|
|
secret_total = pr_global_struct->total_secrets;
|
|
secret_found = pr_global_struct->found_secrets;
|
|
monsters_total = pr_global_struct->total_monsters;
|
|
monsters_found = pr_global_struct->killed_monsters;
|
|
}
|
|
ClientReliableWrite_Begin (host_client, svcqw_updatestatlong, 6);
|
|
ClientReliableWrite_Byte (host_client, STAT_TOTALSECRETS);
|
|
ClientReliableWrite_Long (host_client, secret_total);
|
|
|
|
ClientReliableWrite_Begin (host_client, svcqw_updatestatlong, 6);
|
|
ClientReliableWrite_Byte (host_client, STAT_TOTALMONSTERS);
|
|
ClientReliableWrite_Long (host_client, monsters_total);
|
|
|
|
ClientReliableWrite_Begin (host_client, svcqw_updatestatlong, 6);
|
|
ClientReliableWrite_Byte (host_client, STAT_SECRETS);
|
|
ClientReliableWrite_Long (host_client, secret_found);
|
|
|
|
ClientReliableWrite_Begin (host_client, svcqw_updatestatlong, 6);
|
|
ClientReliableWrite_Byte (host_client, STAT_MONSTERS);
|
|
ClientReliableWrite_Long (host_client, monsters_found);
|
|
// 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;
|
|
|
|
VectorClear (sv_player->v->origin);
|
|
VectorClear (sv_player->v->view_ofs);
|
|
sv_player->v->view_ofs[2] = DEFAULT_VIEWHEIGHT;
|
|
sv_player->v->movetype = MOVETYPE_NOCLIP;
|
|
|
|
// search for an info_playerstart to spawn the spectator at
|
|
//this is only useful when a mod doesn't nativly support spectators. old qw on nq mods.
|
|
|
|
for (i=MAX_CLIENTS+1 ; i<sv.world.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;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void SV_Begin_Core(client_t *split)
|
|
{ //this is the client-protocol-independant core, for q1/q2 gamecode
|
|
|
|
client_t *oh;
|
|
int i;
|
|
if (progstype == PROG_H2 && split->playerclass)
|
|
split->edict->xv->playerclass = split->playerclass; //make sure it's set the same as the userinfo
|
|
|
|
#ifdef Q2SERVER
|
|
if (ge)
|
|
{
|
|
ge->ClientUserinfoChanged (split->q2edict, split->userinfo); //tell the gamecode
|
|
SV_ExtractFromUserinfo(split, true); //let the server routines know
|
|
|
|
ge->ClientBegin(split->q2edict);
|
|
split->istobeloaded = false;
|
|
sv.spawned_client_slots++;
|
|
}
|
|
else
|
|
#endif
|
|
if (split->istobeloaded)
|
|
{
|
|
func_t f;
|
|
split->istobeloaded = false;
|
|
|
|
f = PR_FindFunction(svprogfuncs, "RestoreGame", PR_ANY);
|
|
if (f)
|
|
{
|
|
pr_global_struct->time = sv.world.physicstime;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
|
|
PR_ExecuteProgram (svprogfuncs, f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (split->spectator)
|
|
{
|
|
SV_SpawnSpectator ();
|
|
|
|
if (SpectatorConnect)
|
|
{
|
|
//keep the spectator tracking the player from the previous map
|
|
if (split->spec_track > 0)
|
|
split->edict->v->goalentity = EDICT_TO_PROG(svprogfuncs, EDICT_NUM(svprogfuncs, split->spec_track));
|
|
else
|
|
split->edict->v->goalentity = 0;
|
|
|
|
|
|
// copy spawn parms out of the client_t
|
|
for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
|
|
{
|
|
if (pr_global_ptrs->spawnparamglobals[i])
|
|
*pr_global_ptrs->spawnparamglobals[i] = split->spawn_parms[i];
|
|
}
|
|
|
|
// call the spawn function
|
|
pr_global_struct->time = sv.world.physicstime;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
|
|
PR_ExecuteProgram (svprogfuncs, SpectatorConnect);
|
|
}
|
|
sv.spawned_observer_slots++;
|
|
}
|
|
else
|
|
{
|
|
sv.spawned_client_slots++;
|
|
|
|
if (svprogfuncs)
|
|
{
|
|
eval_t *eval, *eval2;
|
|
eval = PR_FindGlobal(svprogfuncs, "ClientReEnter", 0, NULL);
|
|
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);
|
|
World_UnlinkEdict((wedict_t*)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++)
|
|
{
|
|
if (pr_global_ptrs->spawnparamglobals[j])
|
|
*pr_global_ptrs->spawnparamglobals[j] = split->spawn_parms[j];
|
|
}
|
|
pr_global_struct->time = sv.world.physicstime;
|
|
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++)
|
|
{
|
|
if (pr_global_ptrs->spawnparamglobals[i])
|
|
*pr_global_ptrs->spawnparamglobals[i] = split->spawn_parms[i];
|
|
}
|
|
|
|
// call the spawn function
|
|
#ifdef VM_Q1
|
|
if (svs.gametype == GT_Q1QVM)
|
|
Q1QVM_ClientConnect(split);
|
|
else
|
|
#endif
|
|
{
|
|
globalvars_t *pr_globals = PR_globals(svprogfuncs, PR_CURRENT);
|
|
|
|
pr_global_struct->time = sv.world.physicstime;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
|
|
if (pr_globals)
|
|
G_FLOAT(OFS_PARM0) = split->csqcactive; //this arg is part of EXT_CSQC_1, but doesn't have to be supported by the mod
|
|
if (pr_global_ptrs->ClientConnect)
|
|
PR_ExecuteProgram (svprogfuncs, *pr_global_ptrs->ClientConnect);
|
|
|
|
// actually spawn the player
|
|
pr_global_struct->time = sv.world.physicstime;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
|
|
if (pr_global_ptrs->PutClientInServer)
|
|
PR_ExecuteProgram (svprogfuncs, *pr_global_ptrs->PutClientInServer);
|
|
else
|
|
{
|
|
split->edict->v->health = 100;
|
|
split->edict->v->mins[0] = -16;
|
|
split->edict->v->mins[1] = -16;
|
|
split->edict->v->mins[2] = -24;
|
|
split->edict->v->maxs[0] = 16;
|
|
split->edict->v->maxs[1] = 16;
|
|
split->edict->v->maxs[2] = 32;
|
|
split->edict->v->movetype = MOVETYPE_NOCLIP;
|
|
}
|
|
}
|
|
|
|
oh = host_client;
|
|
host_client = split;
|
|
sv_player = host_client->edict;
|
|
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;
|
|
sv_player = host_client->edict;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Begin_f
|
|
==================
|
|
*/
|
|
void SV_Begin_f (void)
|
|
{
|
|
client_t *split;
|
|
unsigned pmodel = 0, emodel = 0;
|
|
qboolean sendangles=false;
|
|
|
|
if (!SV_CheckRealIP(host_client, true))
|
|
{
|
|
if (host_client->protocol == SCP_QUAKE2)
|
|
ClientReliableWrite_Begin (host_client, svcq2_stufftext, 13+strlen(Cmd_Args()));
|
|
else
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 13+strlen(Cmd_Args()));
|
|
ClientReliableWrite_String (host_client, va("cmd begin %s\n", Cmd_Args()));
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (host_client->istobeloaded)
|
|
sendangles = true;
|
|
if (host_client->protocol == SCP_QUAKE2)
|
|
sendangles = false;
|
|
|
|
|
|
for (split = host_client; split; split = split->controlled)
|
|
{ //tell the gamecode they're ready
|
|
SV_Begin_Core(split);
|
|
}
|
|
|
|
// 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 (!ISQ2CLIENT(host_client))
|
|
{
|
|
ClientReliableWrite_Begin (host_client, svc_setpause, 2);
|
|
ClientReliableWrite_Byte (host_client, sv.paused!=0);
|
|
}
|
|
if (sv.paused&~4)
|
|
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 );
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
//dp downloads are a 2-stream system
|
|
//the server->client stream is as you'd expect. except that its unreliable rather than reliable
|
|
//the client->server stream contains no actual data.
|
|
//when c2s has a hole, the s2c stream is reset to the last-known 'good' position.
|
|
//eventually the client is left acking packets with no data in them, the server then tells the client that the download is complete.
|
|
//the client does no checks to see if there's a hole, other than the crc
|
|
|
|
//so any single lost packet (even client->server) means that the entire stream will be set back by your ping time
|
|
void SV_DarkPlacesDownloadChunk(client_t *cl, sizebuf_t *msg)
|
|
{
|
|
#define MAXDPDOWNLOADCHUNK 1024
|
|
char buffer[MAXDPDOWNLOADCHUNK];
|
|
|
|
int size, start;
|
|
|
|
if (!ISNQCLIENT(cl))
|
|
return;
|
|
if (!cl->download)
|
|
return;
|
|
|
|
if (!cl->downloadstarted)
|
|
return;
|
|
|
|
if (cl->num_backbuf)
|
|
return;
|
|
|
|
size = 1024; //fixme
|
|
|
|
if (size > cl->datagram.maxsize - cl->datagram.cursize)
|
|
size = cl->datagram.maxsize - cl->datagram.cursize - 16;
|
|
|
|
if (size > MAXDPDOWNLOADCHUNK) //don't clog it too much
|
|
size = MAXDPDOWNLOADCHUNK;
|
|
|
|
start = VFS_TELL(cl->download);
|
|
if (start+size > cl->downloadsize) //clamp to the size of the file.
|
|
size = cl->downloadsize - start;
|
|
|
|
size = VFS_READ(cl->download, buffer, size);
|
|
if (size < 0)
|
|
size = 0;
|
|
|
|
MSG_WriteByte(msg, svcdp_downloaddata);
|
|
MSG_WriteLong (msg, start);
|
|
MSG_WriteShort (msg, size);
|
|
SZ_Write(msg, buffer, size);
|
|
}
|
|
|
|
void SVDP_StartDownload_f(void)
|
|
{
|
|
if (host_client->protocol != SCP_DARKPLACES7)
|
|
return;
|
|
if (!host_client->download)
|
|
return;
|
|
host_client->downloadstarted = true;
|
|
}
|
|
|
|
void SV_DarkPlacesDownloadAck(client_t *cl)
|
|
{
|
|
int start = MSG_ReadLong();
|
|
int size = (unsigned short)MSG_ReadShort();
|
|
|
|
if (!cl->download)
|
|
return;
|
|
|
|
if (start != cl->downloadacked)
|
|
{
|
|
//packetloss
|
|
VFS_SEEK(cl->download, cl->downloadacked);
|
|
}
|
|
else if (size != 0)
|
|
{
|
|
cl->downloadacked += size; //successful packet
|
|
cl->downloadcount = cl->downloadacked;
|
|
}
|
|
else
|
|
{
|
|
char *s;
|
|
unsigned short crc;
|
|
int pos=0, csize;
|
|
qbyte chunk[1024];
|
|
QCRC_Init(&crc);
|
|
VFS_SEEK(host_client->download, 0);
|
|
while (pos < host_client->downloadsize)
|
|
{
|
|
csize = sizeof(chunk);
|
|
if (pos + csize > host_client->downloadsize)
|
|
csize = host_client->downloadsize - pos;
|
|
VFS_READ(host_client->download, chunk, csize);
|
|
QCRC_AddBlock(&crc, chunk, csize);
|
|
pos += csize;
|
|
}
|
|
|
|
s = va("\ncl_downloadfinished %i %i \"\"\n", host_client->downloadsize, crc);
|
|
ClientReliableWrite_Begin (cl, svc_stufftext, 2+strlen(s));
|
|
ClientReliableWrite_String(cl, s);
|
|
|
|
VFS_CLOSE(host_client->download);
|
|
host_client->send_message = true;
|
|
host_client->download = NULL;
|
|
host_client->downloadsize = 0;
|
|
}
|
|
}
|
|
|
|
void SV_NextChunkedDownload(unsigned int chunknum, int ezpercent, int ezfilenum)
|
|
{
|
|
#define CHUNKSIZE 1024
|
|
char buffer[CHUNKSIZE];
|
|
qbyte oobdata[1+ (sizeof("\\chunk")-1) + 4 + 1 + 4 + CHUNKSIZE];
|
|
sizebuf_t *msg, msg_oob;
|
|
int i;
|
|
int error = false;
|
|
|
|
msg = &host_client->datagram;
|
|
|
|
if (chunknum*CHUNKSIZE > host_client->downloadsize)
|
|
{
|
|
SV_ClientPrintf (host_client, PRINT_HIGH, "Invalid file chunk requested %u to %u of %u.\n", chunknum*CHUNKSIZE, (chunknum+1)*CHUNKSIZE, host_client->downloadsize);
|
|
error = 2;
|
|
}
|
|
|
|
if (!error && VFS_SEEK (host_client->download, chunknum*CHUNKSIZE) == false)
|
|
error = true;
|
|
else
|
|
{
|
|
if (host_client->downloadcount < chunknum*CHUNKSIZE)
|
|
host_client->downloadcount = chunknum*CHUNKSIZE;
|
|
}
|
|
|
|
if ((host_client->datagram.cursize + CHUNKSIZE+5+50 > host_client->datagram.maxsize) || (host_client->datagram.cursize + CHUNKSIZE+5 > 1400))
|
|
{
|
|
//would overflow the packet, or result in (ethernet) fragmentation and high packet loss.
|
|
msg = &msg_oob;
|
|
|
|
if (!ezfilenum)
|
|
return;
|
|
|
|
if (host_client->waschoked)
|
|
return; //don't let chunked downloads flood out the standard packets.
|
|
|
|
if (!Netchan_CanPacket(&host_client->netchan, SV_RateForClient(host_client)))
|
|
return;
|
|
}
|
|
|
|
if (error)
|
|
i = 0;
|
|
else
|
|
i = VFS_READ (host_client->download, buffer, CHUNKSIZE);
|
|
|
|
if (i > 0)
|
|
{
|
|
if (msg == &msg_oob)
|
|
{
|
|
msg = &msg_oob;
|
|
msg->cursize = 0;
|
|
msg->maxsize = sizeof(oobdata);
|
|
msg->currentbit = 0;
|
|
msg->packing = SZ_RAWBYTES;
|
|
msg->allowoverflow = 0;
|
|
msg->overflowed = 0;
|
|
msg->data = oobdata;
|
|
MSG_WriteByte(msg, A2C_PRINT);
|
|
SZ_Write(msg, "\\chunk", 6);
|
|
MSG_WriteLong(msg, ezfilenum); //echoing the file num is used so the packets don't go out of sync.
|
|
}
|
|
|
|
if (i != CHUNKSIZE)
|
|
memset(buffer+i, 0, CHUNKSIZE-i);
|
|
|
|
MSG_WriteByte(msg, svc_download);
|
|
MSG_WriteLong(msg, chunknum);
|
|
SZ_Write(msg, buffer, CHUNKSIZE);
|
|
|
|
if (msg == &msg_oob)
|
|
{
|
|
Netchan_OutOfBand(NS_SERVER, &host_client->netchan.remote_address, msg_oob.cursize, msg_oob.data);
|
|
Netchan_Block(&host_client->netchan, msg_oob.cursize, SV_RateForClient(host_client));
|
|
}
|
|
}
|
|
else if (i < 0)
|
|
error = true;
|
|
|
|
if (error)
|
|
{
|
|
VFS_CLOSE (host_client->download);
|
|
host_client->download = NULL;
|
|
|
|
if (error != 2)
|
|
{/*work around for ezquake*/
|
|
ClientReliableWrite_Begin (host_client, svc_download, 10+strlen(host_client->downloadfn));
|
|
ClientReliableWrite_Long (host_client, -1);
|
|
ClientReliableWrite_Long (host_client, -3);
|
|
ClientReliableWrite_String (host_client, host_client->downloadfn);
|
|
}
|
|
|
|
|
|
host_client->downloadstarted = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_NextDownload_f
|
|
==================
|
|
*/
|
|
void SV_NextDownload_f (void)
|
|
{
|
|
qbyte buffer[1024];
|
|
int r;
|
|
int percent;
|
|
int size;
|
|
|
|
if (!host_client->download)
|
|
return;
|
|
|
|
#ifdef PEXT_CHUNKEDDOWNLOADS
|
|
if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS)
|
|
{
|
|
if (Cmd_Argc() < 2)
|
|
SV_NextChunkedDownload(atoi(Cmd_Argv(1)), atoi(Cmd_Argv(2)), atoi(Cmd_Argv(3)));
|
|
else
|
|
SV_NextChunkedDownload(atoi(Cmd_Argv(1)), atoi(Cmd_Argv(2)), atoi(Cmd_Argv(3)));
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
r = host_client->downloadsize - host_client->downloadcount;
|
|
/*
|
|
#ifdef PEXT_ZLIBDL
|
|
if (host_client->protocolextensions & PEXT_ZLIBDL)
|
|
{
|
|
if (r>1024) //expect a little more.
|
|
r=1024;
|
|
}
|
|
else
|
|
#endif
|
|
*/
|
|
if (r > 768)
|
|
r = 768;
|
|
r = VFS_READ (host_client->download, buffer, r);
|
|
ClientReliableWrite_Begin (host_client, ISQ2CLIENT(host_client)?svcq2_download:svc_download, 6+r);
|
|
ClientReliableWrite_Short (host_client, r);
|
|
|
|
host_client->downloadcount += r;
|
|
size = host_client->downloadsize;
|
|
|
|
if (host_client->downloadcount < size)
|
|
{
|
|
if (!size)
|
|
size = 1;
|
|
|
|
percent = (double)host_client->downloadcount*100.0/size;
|
|
percent = bound(0, percent, 99);
|
|
}
|
|
else
|
|
percent = 100;
|
|
|
|
#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;
|
|
|
|
VFS_CLOSE (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);
|
|
vsnprintf (send+5, sizeof(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\n");
|
|
|
|
// suck out rest of packet
|
|
size = MSG_ReadShort (); MSG_ReadByte ();
|
|
msg_readcount += size;
|
|
return;
|
|
}
|
|
|
|
size = MSG_ReadShort ();
|
|
percent = MSG_ReadByte ();
|
|
|
|
if (!host_client->upload)
|
|
{
|
|
FS_CreatePath(host_client->uploadfn, FS_GAMEONLY);
|
|
host_client->upload = FS_OpenVFS(host_client->uploadfn, "wb", FS_GAMEONLY);
|
|
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\n");
|
|
*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);
|
|
}
|
|
|
|
VFS_WRITE (host_client->upload, net_message.data + msg_readcount, size);
|
|
msg_readcount += size;
|
|
|
|
if (percent != 100)
|
|
{
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
|
|
ClientReliableWrite_String (host_client, "nextul\n");
|
|
}
|
|
else
|
|
{
|
|
VFS_CLOSE (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);
|
|
}
|
|
*host_client->uploadfn = 0; //don't let it get overwritten again
|
|
}
|
|
}
|
|
|
|
#ifdef VOICECHAT
|
|
/*
|
|
Pivicy issues:
|
|
By sending voice chat to a server, you are unsure who might be listening.
|
|
Voice can be recorded to an mvd, potentially including voice.
|
|
Spectators tracvking you are able to hear team chat of your team.
|
|
You're never quite sure if anyone might join the server and your team before you finish saying a sentance.
|
|
You run the risk of sounds around you being recorded by quake, including but not limited to: TV channels, loved ones, phones, YouTube videos featuring certain moans.
|
|
Default on non-team games is to broadcast.
|
|
*/
|
|
|
|
#define VOICE_RING_SIZE 512 /*POT*/
|
|
struct
|
|
{
|
|
struct voice_ring_s
|
|
{
|
|
unsigned int sender;
|
|
unsigned char receiver[(MAX_CLIENTS+7)/8];
|
|
unsigned char gen;
|
|
unsigned char seq;
|
|
unsigned int datalen;
|
|
unsigned char data[1024];
|
|
} ring[VOICE_RING_SIZE];
|
|
unsigned int write;
|
|
} voice;
|
|
void SV_VoiceReadPacket(void)
|
|
{
|
|
unsigned int vt = host_client->voice_target;
|
|
unsigned int j;
|
|
struct voice_ring_s *ring;
|
|
unsigned short bytes;
|
|
client_t *cl;
|
|
unsigned char gen = MSG_ReadByte();
|
|
unsigned char seq = MSG_ReadByte();
|
|
/*read the data from the client*/
|
|
bytes = MSG_ReadShort();
|
|
ring = &voice.ring[voice.write & (VOICE_RING_SIZE-1)];
|
|
if (bytes > sizeof(ring->data) || host_client->ismuted || !sv_voip.ival)
|
|
{
|
|
MSG_ReadSkip(bytes);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
voice.write++;
|
|
MSG_ReadData(ring->data, bytes);
|
|
}
|
|
|
|
ring->datalen = bytes;
|
|
ring->sender = host_client - svs.clients;
|
|
ring->gen = gen;
|
|
ring->seq = seq;
|
|
|
|
/*broadcast it its to their team, and its not teamplay*/
|
|
if (vt == VT_TEAM && !teamplay.ival)
|
|
vt = VT_ALL;
|
|
|
|
/*figure out which team members are meant to receive it*/
|
|
for (j = 0; j < (MAX_CLIENTS+7)/8; j++)
|
|
ring->receiver[j] = 0;
|
|
for (j = 0, cl = svs.clients; j < sv.allocated_client_slots; j++, cl++)
|
|
{
|
|
if (cl->state != cs_spawned && cl->state != cs_connected)
|
|
continue;
|
|
/*spectators may only talk to spectators*/
|
|
if (host_client->spectator && !sv_spectalk.ival)
|
|
if (!cl->spectator)
|
|
continue;
|
|
|
|
if (vt == VT_TEAM)
|
|
{
|
|
// the spectator team
|
|
if (host_client->spectator)
|
|
{
|
|
if (!cl->spectator)
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (strcmp(cl->team, host_client->team) || cl->spectator)
|
|
continue; // on different teams
|
|
}
|
|
}
|
|
else if (vt == VT_NONMUTED)
|
|
{
|
|
if (host_client->voice_mute[j>>3] & (1<<(j&3)))
|
|
continue;
|
|
}
|
|
else if (vt >= VT_PLAYERSLOT0)
|
|
{
|
|
if (j != vt - VT_PLAYERSLOT0)
|
|
continue;
|
|
}
|
|
|
|
ring->receiver[j>>3] |= 1<<(j&3);
|
|
}
|
|
|
|
if (sv.mvdrecording && sv_voip_record.ival && !(sv_voip_record.ival == 2 && !host_client->spectator))
|
|
{
|
|
sizebuf_t *msg;
|
|
// non-team messages should be seen always, even if not tracking any player
|
|
if (vt == VT_ALL && (!host_client->spectator || sv_spectalk.ival))
|
|
{
|
|
msg = MVDWrite_Begin (dem_all, 0, ring->datalen+6);
|
|
}
|
|
else
|
|
{
|
|
unsigned int cls;
|
|
cls = ring->receiver[0] |
|
|
(ring->receiver[1]<<8) |
|
|
(ring->receiver[2]<<16) |
|
|
(ring->receiver[3]<<24);
|
|
msg = MVDWrite_Begin (dem_multiple, cls, ring->datalen+6);
|
|
}
|
|
|
|
MSG_WriteByte(msg, svcfte_voicechat);
|
|
MSG_WriteByte(msg, ring->sender);
|
|
MSG_WriteByte(msg, ring->gen);
|
|
MSG_WriteByte(msg, ring->seq);
|
|
MSG_WriteShort(msg, ring->datalen);
|
|
SZ_Write(msg, ring->data, ring->datalen);
|
|
}
|
|
}
|
|
void SV_VoiceInitClient(client_t *client)
|
|
{
|
|
client->voice_target = VT_TEAM;
|
|
client->voice_active = false;
|
|
client->voice_read = voice.write;
|
|
memset(client->voice_mute, 0, sizeof(client->voice_mute));
|
|
}
|
|
void SV_VoiceSendPacket(client_t *client, sizebuf_t *buf)
|
|
{
|
|
unsigned int clno;
|
|
qboolean send;
|
|
struct voice_ring_s *ring;
|
|
|
|
if (client->controller)
|
|
client = client->controller;
|
|
clno = client - svs.clients;
|
|
|
|
if (!(client->fteprotocolextensions2 & PEXT2_VOICECHAT))
|
|
return;
|
|
if (!client->voice_active || client->num_backbuf)
|
|
{
|
|
client->voice_read = voice.write;
|
|
return;
|
|
}
|
|
|
|
while(client->voice_read < voice.write)
|
|
{
|
|
/*they might be too far behind*/
|
|
if (client->voice_read+VOICE_RING_SIZE < voice.write)
|
|
client->voice_read = voice.write - VOICE_RING_SIZE;
|
|
|
|
ring = &voice.ring[(client->voice_read) & (VOICE_RING_SIZE-1)];
|
|
|
|
/*figure out if it was for us*/
|
|
send = false;
|
|
if (ring->receiver[clno>>3] & (1<<(clno&3)))
|
|
send = true;
|
|
|
|
/*if you're spectating, you can hear whatever your tracked player can hear*/
|
|
if (host_client->spectator && host_client->spec_track && host_client->spec_track <= sv.allocated_client_slots)
|
|
if (ring->receiver[(host_client->spec_track-1)>>3] & (1<<((host_client->spec_track-1)&3)))
|
|
send = true;
|
|
|
|
|
|
if (client->voice_mute[ring->sender>>3] & (1<<(ring->sender&3)))
|
|
send = false;
|
|
|
|
if (ring->sender == clno && !sv_voip_echo.ival)
|
|
send = false;
|
|
|
|
/*additional ways to block voice*/
|
|
if (client->download)
|
|
send = false;
|
|
|
|
if (send)
|
|
{
|
|
if (buf->maxsize - buf->cursize < ring->datalen+5)
|
|
break;
|
|
MSG_WriteByte(buf, svcfte_voicechat);
|
|
MSG_WriteByte(buf, ring->sender);
|
|
MSG_WriteByte(buf, ring->gen);
|
|
MSG_WriteByte(buf, ring->seq);
|
|
MSG_WriteShort(buf, ring->datalen);
|
|
SZ_Write(buf, ring->data, ring->datalen);
|
|
}
|
|
client->voice_read++;
|
|
}
|
|
}
|
|
|
|
void SV_Voice_Ignore_f(void)
|
|
{
|
|
unsigned int other;
|
|
int type = 0;
|
|
|
|
if (Cmd_Argc() < 2)
|
|
{
|
|
/*only a name = toggle*/
|
|
type = 0;
|
|
}
|
|
else
|
|
{
|
|
/*mute if 1, unmute if 0*/
|
|
if (atoi(Cmd_Argv(2)))
|
|
type = 1;
|
|
else
|
|
type = -1;
|
|
}
|
|
other = atoi(Cmd_Argv(1));
|
|
if (other >= MAX_CLIENTS)
|
|
return;
|
|
|
|
switch(type)
|
|
{
|
|
case -1:
|
|
host_client->voice_mute[other>>3] &= ~(1<<(other&3));
|
|
break;
|
|
case 0:
|
|
host_client->voice_mute[other>>3] ^= (1<<(other&3));
|
|
break;
|
|
case 1:
|
|
host_client->voice_mute[other>>3] |= (1<<(other&3));
|
|
}
|
|
}
|
|
void SV_Voice_Target_f(void)
|
|
{
|
|
unsigned int other;
|
|
char *t = Cmd_Argv(1);
|
|
if (!strcmp(t, "team"))
|
|
host_client->voice_target = VT_TEAM;
|
|
else if (!strcmp(t, "all"))
|
|
host_client->voice_target = VT_ALL;
|
|
else if (!strcmp(t, "nonmuted"))
|
|
host_client->voice_target = VT_NONMUTED;
|
|
else if (*t >= '0' && *t <= '9')
|
|
{
|
|
other = atoi(t);
|
|
if (other >= MAX_CLIENTS)
|
|
return;
|
|
host_client->voice_target = VT_PLAYERSLOT0 + other;
|
|
}
|
|
else
|
|
{
|
|
/*don't know who you mean, futureproofing*/
|
|
host_client->voice_target = VT_TEAM;
|
|
}
|
|
}
|
|
void SV_Voice_MuteAll_f(void)
|
|
{
|
|
host_client->voice_active = false;
|
|
}
|
|
void SV_Voice_UnmuteAll_f(void)
|
|
{
|
|
host_client->voice_active = true;
|
|
}
|
|
#endif
|
|
|
|
//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 (const 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_packages;
|
|
extern cvar_t allow_download_wads;
|
|
extern cvar_t allow_download_root;
|
|
extern cvar_t allow_download_configs;
|
|
extern cvar_t allow_download_copyrighted;
|
|
char cleanname[MAX_QPATH];
|
|
int i=0;
|
|
if (strlen(name) >= MAX_QPATH)
|
|
return false;
|
|
do
|
|
{
|
|
cleanname[i++] = *name;
|
|
} while(*name++);
|
|
name = cleanname;
|
|
|
|
//allowed at all?
|
|
if (!allow_download.ival)
|
|
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, "package/", 8))
|
|
{
|
|
if (!strcmp("pk4", COM_FileExtension(name)) || !strcmp("pk3", COM_FileExtension(name)) || !strcmp("pak", COM_FileExtension(name)))
|
|
{
|
|
if (!allow_download_packages.ival)
|
|
return false;
|
|
/*do not permit 'id1/pak1.pak' or 'baseq3/pak0.pk3' or any similarly named packages. such packages would violate copyright, and must be obtained through other means (like buying the damn game)*/
|
|
if (!allow_download_copyrighted.ival && !FS_GetPackageDownloadable(name+8))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (strncmp(name, "maps/", 5) == 0)
|
|
return !!allow_download_maps.value;
|
|
|
|
//skins?
|
|
if (strncmp(name, "skins/", 6) == 0)
|
|
return !!allow_download_skins.value;
|
|
//models
|
|
if ((strncmp(name, "progs/", 6) == 0) ||
|
|
(strncmp(name, "models/", 7) == 0))
|
|
return !!allow_download_models.value;
|
|
//sound
|
|
if (strncmp(name, "sound/", 6) == 0)
|
|
return !!allow_download_sounds.value;
|
|
//demos
|
|
if (strncmp(name, "demos/", 6) == 0)
|
|
return !!allow_download_demos.value;
|
|
|
|
//textures
|
|
if (strncmp(name, "textures/", 9) == 0)
|
|
return !!allow_download_textures.value;
|
|
|
|
//wads
|
|
if (strncmp(name, "wads/", 5) == 0)
|
|
return !!allow_download_wads.value;
|
|
if (!strcmp("wad", COM_FileExtension(name)))
|
|
return !!allow_download_wads.value;
|
|
|
|
//pak/pk3s.
|
|
if (!strcmp("pk4", COM_FileExtension(name)) || !strcmp("pk3", COM_FileExtension(name)) || !strcmp("pak", COM_FileExtension(name)))
|
|
{
|
|
if (strnicmp(name, "pak", 3)) //don't give out core pak/pk3 files. This matches q3 logic.
|
|
return !!allow_download_packages.value;
|
|
else
|
|
return !!allow_download_copyrighted.value;
|
|
}
|
|
|
|
if (!strcmp("cfg", COM_FileExtension(name)))
|
|
return !!allow_download_configs.value;
|
|
|
|
//root of gamedir
|
|
if (!strchr(name, '/') && !allow_download_root.value)
|
|
{
|
|
if (strcmp(name, "csprogs.dat")) //we always allow csprogs.dat to be downloaded (if downloads are permitted).
|
|
return false;
|
|
}
|
|
|
|
//any other subdirs are allowed
|
|
return true;
|
|
}
|
|
|
|
static int SV_LocateDownload(char *name, flocation_t *loc, char **replacementname, qboolean redirectpaks)
|
|
{
|
|
extern cvar_t allow_download_anymap, allow_download_pakcontents;
|
|
extern cvar_t sv_demoDir;
|
|
qboolean protectedpak;
|
|
qboolean found;
|
|
|
|
if (replacementname)
|
|
*replacementname = NULL;
|
|
|
|
//mangle the name by making it lower case.
|
|
{
|
|
char *p;
|
|
|
|
for (p = name; *p; p++)
|
|
*p = (char)tolower((unsigned char)*p);
|
|
}
|
|
|
|
|
|
|
|
if (!SV_AllowDownload(name))
|
|
{
|
|
Sys_Printf ("%s denied download of %s due to path/name rules\n", host_client->name, name);
|
|
return -2; //not permitted (even if it exists).
|
|
}
|
|
|
|
//mvdsv demo downloading support demonum/ -> demos/XXXX (sets up the client paths)
|
|
if (!strncmp(name, "demonum/", 8))
|
|
{
|
|
if (replacementname)
|
|
{
|
|
char mvdnamebuffer[MAX_QPATH];
|
|
char *mvdname = SV_MVDNum(mvdnamebuffer, sizeof(mvdnamebuffer), atoi(name+8));
|
|
if (!mvdname)
|
|
{
|
|
SV_ClientPrintf (host_client, PRINT_HIGH, "%s is an invalid MVD demonum.\n", name+8);
|
|
Sys_Printf ("%s requested invalid demonum %s\n", host_client->name, name+8);
|
|
return -1; //not found
|
|
}
|
|
*replacementname = va("demos/%s\n", mvdname);
|
|
return -4; //redirect
|
|
}
|
|
}
|
|
|
|
//mvdsv demo downloading support. demos/ -> demodir (sets up the server paths)
|
|
if (!strncmp(name, "demos/", 6))
|
|
name = va("%s/%s", sv_demoDir.string, name+6);
|
|
|
|
if (!strncmp(name, "package/", 8))
|
|
{
|
|
vfsfile_t *f = FS_OpenVFS(name+8, "rb", FS_ROOT);
|
|
if (f)
|
|
{
|
|
VFS_CLOSE(f);
|
|
return -5; //found package
|
|
}
|
|
else
|
|
return -1; //not found/unable to open
|
|
}
|
|
else if (Terrain_LocateSection(name, loc))
|
|
{
|
|
found = true;
|
|
}
|
|
else
|
|
found = FS_FLocateFile(name, FSLFRT_IFFOUND, loc);
|
|
|
|
//nexuiz names certain files as .wav but they're really .ogg on disk.
|
|
if (!found && replacementname)
|
|
{
|
|
if (!strcmp(COM_FileExtension(name), "wav"))
|
|
{
|
|
char tryogg[MAX_QPATH];
|
|
COM_StripExtension(name, tryogg, sizeof(tryogg));
|
|
COM_DefaultExtension(tryogg, ".ogg", sizeof(tryogg));
|
|
|
|
found = FS_FLocateFile(tryogg, FSLFRT_IFFOUND, loc);
|
|
if (found)
|
|
{
|
|
name = *replacementname = va("%s", tryogg);
|
|
}
|
|
}
|
|
}
|
|
//nexuiz also names files with absolute paths (yet sounds are meant to have an extra prefix)
|
|
//this results in clients asking for sound/sound/blah.wav (or sound/sound/blah.ogg for nexuiz)
|
|
if (!found && replacementname)
|
|
{
|
|
if (!strncmp(name, "sound/", 6))
|
|
{
|
|
int result;
|
|
result = SV_LocateDownload(name+6, loc, replacementname, redirectpaks);
|
|
if (!result)
|
|
{ //if that was successful... redirect to it.
|
|
result = -4;
|
|
*replacementname = name+6;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (found)
|
|
{
|
|
protectedpak = com_file_copyprotected;
|
|
|
|
// special check for maps, if it came from a pak file, don't allow download
|
|
if (protectedpak)
|
|
{
|
|
if (!allow_download_anymap.value && !strncmp(name, "maps/", 5))
|
|
{
|
|
Sys_Printf ("%s denied download of %s - it is in a pak\n", host_client->name, name+8);
|
|
return -2;
|
|
}
|
|
}
|
|
|
|
if (replacementname)
|
|
{
|
|
char *pakname = FS_WhichPackForLocation(loc);
|
|
if (pakname && SV_AllowDownload(pakname))
|
|
{
|
|
//return loc of the pak instead.
|
|
if (FS_FLocateFile(name, FSLFRT_IFFOUND, loc))
|
|
{
|
|
//its inside a pak file, return the name of this file instead
|
|
*replacementname = pakname;
|
|
return -4; //redirect
|
|
}
|
|
else
|
|
Con_Printf("Failed to read %s\n", pakname);
|
|
}
|
|
}
|
|
|
|
if (protectedpak)
|
|
{ //if its in a pak file, don't allow downloads if we don't allow the contents of paks to be sent.
|
|
if (!allow_download_pakcontents.value)
|
|
{
|
|
Sys_Printf ("%s denied download of %s - it is in a pak\n", host_client->name, name+8);
|
|
return -2;
|
|
}
|
|
}
|
|
|
|
if (replacementname && *replacementname)
|
|
return -4;
|
|
return 0;
|
|
}
|
|
return -1; //not found
|
|
}
|
|
|
|
//this function is only meaningful for nq/qw
|
|
void SV_DownloadSize_f(void)
|
|
{
|
|
flocation_t loc;
|
|
char *name = Cmd_Argv(1);
|
|
char *redirected = "";
|
|
|
|
switch(SV_LocateDownload(name, &loc, &redirected, true))
|
|
{
|
|
case -4: /*redirect*/
|
|
name = va("dlsize \"%s\" r \"%s\"\n", name, redirected);
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(name));
|
|
ClientReliableWrite_String (host_client, name);
|
|
break;
|
|
default:
|
|
case -1: /*not found*/
|
|
name = va("dlsize \"%s\" e\n", name);
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(name));
|
|
ClientReliableWrite_String (host_client, name);
|
|
break;
|
|
case -2: /*permission*/
|
|
name = va("dlsize \"%s\" p\n", name);
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(name));
|
|
ClientReliableWrite_String (host_client, name);
|
|
break;
|
|
case -5: /*package*/
|
|
case 0: /*exists*/
|
|
name = va("dlsize \"%s\" %u\n", name, loc.len);
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(name));
|
|
ClientReliableWrite_String (host_client, name);
|
|
break;
|
|
case -6: /*heightmap section*/
|
|
//don't reply.
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_BeginDownload_f
|
|
==================
|
|
*/
|
|
void SV_BeginDownload_f(void)
|
|
{
|
|
char *name = Cmd_Argv(1);
|
|
char *redirection = NULL;
|
|
extern cvar_t allow_download_anymap, allow_download_pakcontents;
|
|
extern cvar_t sv_demoDir;
|
|
flocation_t loc;
|
|
int result;
|
|
|
|
if (ISNQCLIENT(host_client) && host_client->protocol != SCP_DARKPLACES7)
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Your client isn't meant to support downloads\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
if (ISQWCLIENT(host_client) && !strcmp(name, "ezquake-security.dll"))
|
|
{
|
|
vfsfile_t *f = FS_OpenVFS("evil.dll", "rb", FS_GAME);
|
|
if (f)
|
|
{
|
|
int chunk, o = 0;
|
|
int l = VFS_GETLEN(f);
|
|
char *data = malloc(l);
|
|
VFS_READ(f, data, l);
|
|
VFS_CLOSE(f);
|
|
|
|
while (o < l)
|
|
{
|
|
chunk = 768;
|
|
if (o + chunk > l)
|
|
chunk = l - o;
|
|
|
|
ClientReliableWrite_Begin (host_client, ISQ2CLIENT(host_client)?svcq2_download:svc_download, 6+chunk);
|
|
ClientReliableWrite_Short (host_client, chunk);
|
|
ClientReliableWrite_Byte (host_client, (o+chunk == l)?100:0); //lame, whatever.
|
|
ClientReliableWrite_SZ (host_client, data + o, chunk);
|
|
o += chunk;
|
|
}
|
|
}
|
|
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 128);
|
|
ClientReliableWrite_String (host_client, "cmd new\n");
|
|
return;
|
|
}
|
|
*/
|
|
|
|
*host_client->downloadfn = 0;
|
|
|
|
if (host_client->download)
|
|
{
|
|
VFS_CLOSE (host_client->download);
|
|
host_client->download = NULL;
|
|
}
|
|
|
|
result = SV_LocateDownload(name, &loc, &redirection, false);
|
|
|
|
if (result == -5)
|
|
{
|
|
//package download
|
|
result = 0;
|
|
host_client->download = FS_OpenVFS(name+8, "rb", FS_ROOT);
|
|
}
|
|
else
|
|
{
|
|
//redirection protocol-specific code goes here.
|
|
if (result == -4)
|
|
{
|
|
#ifdef PEXT_CHUNKEDDOWNLOADS
|
|
if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS)
|
|
{
|
|
//redirect the client (before the message saying download failed)
|
|
char *s = va("dlsize \"%s\" r \"%s\"\n", name, redirection);
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(s));
|
|
ClientReliableWrite_String (host_client, s);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (result == 0)
|
|
{ //if we are allowed and could find it
|
|
host_client->download = FS_OpenReadLocation(&loc);
|
|
}
|
|
}
|
|
|
|
if (!host_client->download)
|
|
result = -1; //this isn't likely, but hey.
|
|
|
|
//handle errors
|
|
if (result != 0)
|
|
{ // don't allow anything with .. path
|
|
char *error;
|
|
switch(result)
|
|
{
|
|
default:
|
|
error = "Download could not be found\n";
|
|
break;
|
|
case -2:
|
|
error = "Download permission denied\n";
|
|
break;
|
|
case -4:
|
|
result = -1;
|
|
error = "Package contents not available individually\n";
|
|
break;
|
|
}
|
|
#ifdef PEXT_CHUNKEDDOWNLOADS
|
|
if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS)
|
|
{
|
|
ClientReliableWrite_Begin (host_client, svc_download, 10+strlen(name));
|
|
ClientReliableWrite_Long (host_client, -1);
|
|
ClientReliableWrite_Long (host_client, result);
|
|
ClientReliableWrite_String (host_client, name);
|
|
}
|
|
else
|
|
#endif
|
|
if (ISNQCLIENT(host_client))
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, error);
|
|
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 2+12);
|
|
ClientReliableWrite_String (host_client, "\nstopdownload\n");
|
|
}
|
|
else
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, error);
|
|
|
|
ClientReliableWrite_Begin (host_client, ISQ2CLIENT(host_client)?svcq2_download:svc_download, 4);
|
|
ClientReliableWrite_Short (host_client, -1);
|
|
ClientReliableWrite_Byte (host_client, 0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
Q_strncpyz(host_client->downloadfn, name, sizeof(host_client->downloadfn));
|
|
host_client->downloadcount = 0;
|
|
|
|
host_client->downloadsize = VFS_GETLEN(host_client->download);
|
|
|
|
#ifdef PEXT_CHUNKEDDOWNLOADS
|
|
if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS)
|
|
{
|
|
if (host_client->download->seekingisabadplan)
|
|
{ //if seeking is a bad plan (for whatever reason - usually because of zip files)
|
|
//create a temp file instead
|
|
int i, len;
|
|
char buffer[8192];
|
|
vfsfile_t *tmp;
|
|
tmp = FS_OpenTemp();
|
|
|
|
for (i = 0; ; i+=len)
|
|
{
|
|
len = sizeof(buffer);
|
|
if (len > host_client->downloadsize-i)
|
|
len = host_client->downloadsize-i;
|
|
if (len == 0)
|
|
break;
|
|
VFS_READ(host_client->download, buffer, len);
|
|
VFS_WRITE(tmp, buffer, len);
|
|
}
|
|
VFS_CLOSE(host_client->download);
|
|
host_client->download = tmp;
|
|
}
|
|
|
|
ClientReliableWrite_Begin (host_client, svc_download, 10+strlen(host_client->downloadfn));
|
|
ClientReliableWrite_Long (host_client, -1);
|
|
ClientReliableWrite_Long (host_client, host_client->downloadsize);
|
|
ClientReliableWrite_String (host_client, host_client->downloadfn);
|
|
}
|
|
else
|
|
#endif
|
|
|
|
if (ISNQCLIENT(host_client))
|
|
{
|
|
char *s = va("\ncl_downloadbegin %i %s\n", host_client->downloadsize, host_client->downloadfn);
|
|
ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(s));
|
|
ClientReliableWrite_String (host_client, s);
|
|
host_client->send_message = true;
|
|
}
|
|
else
|
|
SV_NextDownload_f ();
|
|
|
|
SV_EndRedirect();
|
|
Con_Printf ("Downloading %s to %s\n", host_client->downloadfn, host_client->name);
|
|
}
|
|
|
|
void SV_StopDownload_f(void)
|
|
{
|
|
//this doesn't mean the download failed or was canceled.
|
|
if (host_client->download)
|
|
{
|
|
VFS_CLOSE (host_client->download);
|
|
host_client->download = NULL;
|
|
}
|
|
else
|
|
SV_ClientPrintf(host_client, PRINT_HIGH, "But you're not downloading anything\n");
|
|
|
|
host_client->downloadstarted = false;
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
|
|
/*
|
|
==================
|
|
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)
|
|
Q_snprintfz (text, sizeof(text), "[SPEC] {%s}:", host_client->name);
|
|
else
|
|
continue;
|
|
}
|
|
else
|
|
Q_snprintfz (text, sizeof(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;
|
|
}
|
|
}
|
|
|
|
float SV_CheckFloodProt(client_t *client)
|
|
{
|
|
if (!sv_floodprotect.value)
|
|
return 0;
|
|
if (sv_floodprotect_messages.value <= 0 || sv_floodprotect_interval.value <= 0)
|
|
return 0;
|
|
if (sv.paused)
|
|
return 0;
|
|
if (realtime < client->lockedtill)
|
|
return client->lockedtill - realtime;
|
|
|
|
if (client->floodprotmessage > sv_floodprotect_messages.value)
|
|
{
|
|
client->lockedtill = realtime + sv_floodprotect_silencetime.value;
|
|
client->floodprotmessage = 0.0;
|
|
client->lastspoke = 0.0;
|
|
if (sv_floodprotect_sendmessage.string[0])
|
|
SV_ClientPrintf(client, PRINT_CHAT, "FloodProt: %s\n", sv_floodprotect_sendmessage.string);
|
|
return sv_floodprotect_silencetime.value;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void SV_PushFloodProt(client_t *client)
|
|
{
|
|
if (!sv_floodprotect.value)
|
|
return;
|
|
if (sv_floodprotect_messages.value <= 0 || sv_floodprotect_interval.value <= 0)
|
|
return;
|
|
if (sv.paused)
|
|
return;
|
|
|
|
if (client->lastspoke)
|
|
{
|
|
client->floodprotmessage -= (realtime - client->lastspoke)
|
|
* sv_floodprotect_messages.value
|
|
/ sv_floodprotect_interval.value;
|
|
client->floodprotmessage = max(0, client->floodprotmessage);
|
|
client->floodprotmessage++;
|
|
}
|
|
else
|
|
client->floodprotmessage = 1.0;
|
|
client->lastspoke = realtime;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Say
|
|
==================
|
|
*/
|
|
void SV_Say (qboolean team)
|
|
{
|
|
client_t *client;
|
|
int j;
|
|
char *p;
|
|
char text[1024];
|
|
char t1[32], *t2;
|
|
int cls = 0;
|
|
float floodtime;
|
|
sizebuf_t *msg;
|
|
|
|
qboolean sent[MAX_CLIENTS]; //so we don't send to the same splitscreen connection twice. (it's ugly)
|
|
int cln;
|
|
|
|
qboolean mvdrecording;
|
|
|
|
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))
|
|
Q_snprintfz (text, sizeof(text), "[SPEC] %s: ", host_client->name);
|
|
else if (team)
|
|
Q_snprintfz (text, sizeof(text), "(%s): ", host_client->name);
|
|
else
|
|
Q_snprintfz (text, sizeof(text), "%s: ", host_client->name);
|
|
|
|
if (host_client->ismuted)
|
|
{
|
|
SV_ClientTPrintf(host_client, PRINT_CHAT,
|
|
STL_MUTEDCHAT);
|
|
return;
|
|
}
|
|
|
|
#ifdef VM_Q1
|
|
if (Q1QVM_ClientSay(sv_player, team))
|
|
return;
|
|
#endif
|
|
|
|
if ((floodtime=SV_CheckFloodProt(host_client)))
|
|
{
|
|
SV_ClientTPrintf(host_client, PRINT_CHAT,
|
|
STL_FLOODPROTTIME,
|
|
(int) (floodtime));
|
|
return;
|
|
}
|
|
SV_PushFloodProt(host_client);
|
|
|
|
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);
|
|
|
|
mvdrecording = sv.mvdrecording;
|
|
sv.mvdrecording = false; //so that the SV_ClientPrintf doesn't send to all players.
|
|
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);
|
|
}
|
|
sv.mvdrecording = mvdrecording;
|
|
|
|
if (!sv.mvdrecording || !cls)
|
|
return;
|
|
|
|
// non-team messages should be seen always, even if not tracking any player
|
|
if (!team && ((host_client->spectator && sv_spectalk.value) || !host_client->spectator))
|
|
msg = MVDWrite_Begin (dem_all, 0, strlen(text)+3);
|
|
else
|
|
msg = MVDWrite_Begin (dem_multiple, cls, strlen(text)+3);
|
|
|
|
MSG_WriteByte (msg, svc_print);
|
|
MSG_WriteByte (msg, PRINT_CHAT);
|
|
MSG_WriteString (msg, 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;
|
|
|
|
#ifdef SERVER_DEMO_PLAYBACK
|
|
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;
|
|
}
|
|
#endif
|
|
if (ISNQCLIENT(host_client))
|
|
{
|
|
char *s;
|
|
ClientReliableWrite_Begin(host_client, svc_stufftext, 15+10*MAX_CLIENTS);
|
|
ClientReliableWrite_SZ(host_client, "pingplreport", 12);
|
|
for (j = 0, client = svs.clients; j < MAX_CLIENTS && j < host_client->max_net_clients; j++, client++)
|
|
{
|
|
s = va(" %i %i", SV_CalcPing(client, false), client->lossage);
|
|
ClientReliableWrite_SZ(host_client, s, strlen(s));
|
|
}
|
|
ClientReliableWrite_Byte (host_client, '\n');
|
|
ClientReliableWrite_Byte (host_client, '\0');
|
|
|
|
}
|
|
else
|
|
{
|
|
for (j = 0, client = svs.clients; j < MAX_CLIENTS && j < host_client->max_net_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, false));
|
|
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)
|
|
{
|
|
float floodtime;
|
|
|
|
#ifdef HLSERVER
|
|
if (svs.gametype == GT_HALFLIFE)
|
|
{
|
|
HLSV_ClientCommand(host_client);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#ifdef VM_Q1
|
|
if (svs.gametype == GT_Q1QVM)
|
|
{
|
|
pr_global_struct->time = sv.world.physicstime;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
|
|
Q1QVM_ClientCommand();
|
|
return;
|
|
}
|
|
#endif
|
|
if (svs.gametype != GT_PROGS)
|
|
return;
|
|
|
|
if (sv_player->v->health <= 0)
|
|
{
|
|
SV_ClientTPrintf (host_client, PRINT_HIGH, STL_NOSUICIDEWHENDEAD);
|
|
return;
|
|
}
|
|
|
|
if (sv_floodprotect_suicide.value)
|
|
{
|
|
if ((floodtime = SV_CheckFloodProt(host_client)))
|
|
{
|
|
SV_ClientPrintf (host_client, PRINT_HIGH, "You can't suicide for %i seconds\n", (int)floodtime);
|
|
return;
|
|
}
|
|
SV_PushFloodProt(host_client);
|
|
}
|
|
|
|
pr_global_struct->time = sv.world.physicstime;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
|
|
|
|
PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientKill);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_TogglePause
|
|
==================
|
|
*/
|
|
qboolean SV_TogglePause (client_t *initiator)
|
|
{
|
|
int newv;
|
|
|
|
newv = sv.paused^1;
|
|
|
|
if (!PR_ShouldTogglePause(initiator, newv))
|
|
return false;
|
|
|
|
sv.paused = newv;
|
|
|
|
sv.pausedstart = Sys_DoubleTime();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
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;
|
|
}
|
|
|
|
if (SV_TogglePause(host_client))
|
|
{
|
|
if (sv.paused & 1)
|
|
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)
|
|
{
|
|
extern cvar_t sv_fullredirect;
|
|
|
|
SV_EndRedirect ();
|
|
if (!host_client->drop)
|
|
{
|
|
if (host_client->redirect)
|
|
SV_BroadcastPrintf (PRINT_HIGH, "%s redirected to %s\n", host_client->name, sv_fullredirect.string);
|
|
else
|
|
{
|
|
if (!host_client->spectator)
|
|
SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTDROPPED, host_client->name);
|
|
}
|
|
host_client->drop = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
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
|
|
#ifdef SERVER_DEMO_PLAYBACK
|
|
&& !sv.demofile
|
|
#endif
|
|
)
|
|
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));
|
|
#ifdef SERVER_DEMO_PLAYBACK
|
|
if (*sv.recordedplayer[i].userinfo)
|
|
{
|
|
host_client->spec_track = i+1;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (!SV_CanTrack(host_client, i+1))
|
|
{
|
|
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)
|
|
{
|
|
if (Cmd_Argc() != 2)
|
|
{
|
|
int rate = SV_RateForClient(host_client);
|
|
if (!rate)
|
|
SV_ClientPrintf (host_client, PRINT_HIGH, "Effective rate is unlimited\n");
|
|
else
|
|
SV_ClientPrintf (host_client, PRINT_HIGH, "Effective rate %i\n", rate);
|
|
return;
|
|
}
|
|
|
|
Info_SetValueForKey (host_client->userinfo, "rate", Cmd_Argv(1), sizeof(host_client->userinfo));
|
|
SV_ExtractFromUserinfo (host_client, true);
|
|
|
|
SV_ClientTPrintf (host_client, PRINT_HIGH, STL_RATESETTO, SV_RateForClient(host_client));
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
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);
|
|
}
|
|
|
|
qboolean SV_UserInfoIsBasic(char *infoname)
|
|
{
|
|
int i;
|
|
char *basicinfos[] = {
|
|
|
|
"name",
|
|
"team",
|
|
"skin",
|
|
"topcolor",
|
|
"bottomcolor",
|
|
|
|
NULL};
|
|
|
|
for (i = 0; basicinfos[i]; i++)
|
|
{
|
|
if (*infoname == '*' || !strcmp(infoname, basicinfos[i]))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_SetInfo_f
|
|
|
|
Allow clients to change userinfo
|
|
==================
|
|
*/
|
|
void SV_SetInfo_f (void)
|
|
{
|
|
int i, j;
|
|
char oldval[MAX_INFO_KEY];
|
|
char *key, *val;
|
|
qboolean basic; //infos that we send to any old qw client.
|
|
client_t *client;
|
|
|
|
|
|
if (Cmd_Argc() == 1)
|
|
{
|
|
SV_ClientPrintf(host_client, PRINT_HIGH, "User info settings:\n");
|
|
Info_Print (host_client->userinfo, "");
|
|
return;
|
|
}
|
|
|
|
if (Cmd_Argc() != 3)
|
|
{
|
|
SV_ClientPrintf(host_client, PRINT_HIGH, "usage: setinfo [ <key> <value> ]\n");
|
|
return;
|
|
}
|
|
|
|
if (Cmd_Argv(1)[0] == '*')
|
|
return; // don't set priveledged values
|
|
|
|
if (strstr(Cmd_Argv(1), "\\") || strstr(Cmd_Argv(2), "\\"))
|
|
return; // illegal char
|
|
|
|
Q_strncpyz(oldval, Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)), sizeof(oldval));
|
|
|
|
#ifdef VM_Q1
|
|
if (Q1QVM_UserInfoChanged(sv_player))
|
|
return;
|
|
#endif
|
|
|
|
Info_SetValueForKey (host_client->userinfo, Cmd_Argv(1), Cmd_Argv(2), sizeof(host_client->userinfo));
|
|
// 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, true);
|
|
|
|
if (progstype != PROG_QW && !strcmp(Cmd_Argv(1), "bottomcolor"))
|
|
{ //team fortress has a nasty habit of booting people without this
|
|
sv_player->v->team = atoi(Cmd_Argv(2))+1;
|
|
}
|
|
|
|
if (*Cmd_Argv(1) != '_')
|
|
{
|
|
i = host_client - svs.clients;
|
|
key = Cmd_Argv(1);
|
|
val = Info_ValueForKey(host_client->userinfo, key);
|
|
|
|
basic = SV_UserInfoIsBasic(key);
|
|
|
|
for (j = 0; j < MAX_CLIENTS; j++)
|
|
{
|
|
client = svs.clients+j;
|
|
if (client->state < cs_connected)
|
|
continue; // reliables go to all connected or spawned
|
|
if (client->controller)
|
|
continue; //splitscreen
|
|
|
|
if (client->protocol == SCP_BAD)
|
|
continue; //botclient
|
|
|
|
if (ISQWCLIENT(client))
|
|
{
|
|
if (basic || (client->fteprotocolextensions & PEXT_BIGUSERINFOS))
|
|
{
|
|
ClientReliableWrite_Begin(client, svc_setinfo, 1+1+strlen(key)+1+strlen(val)+1);
|
|
ClientReliableWrite_Byte(client, i);
|
|
ClientReliableWrite_String(client, key);
|
|
ClientReliableWrite_String(client, val);
|
|
}
|
|
}
|
|
if (ISNQCLIENT(client))
|
|
{
|
|
if (!strcmp(key, "name"))
|
|
{
|
|
ClientReliableWrite_Begin(client, svc_updatename, 1+1+strlen(val)+1);
|
|
ClientReliableWrite_Byte(client, i);
|
|
ClientReliableWrite_String(client, val);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sv.mvdrecording && (basic || (demo.recorder.fteprotocolextensions & PEXT_BIGUSERINFOS)))
|
|
{
|
|
sizebuf_t *msg = MVDWrite_Begin (dem_all, 0, strlen(key)+strlen(val)+4);
|
|
MSG_WriteByte (msg, svc_setinfo);
|
|
MSG_WriteByte (msg, i);
|
|
MSG_WriteString (msg, key);
|
|
MSG_WriteString (msg, val);
|
|
}
|
|
}
|
|
|
|
SV_LogPlayer(host_client, "userinfo changed");
|
|
|
|
PR_ClientUserInfoChanged(Cmd_Argv(1), oldval, Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)));
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_ShowServerinfo_f
|
|
|
|
Dumps the serverinfo info string
|
|
==================
|
|
*/
|
|
void SV_ShowServerinfo_f (void)
|
|
{
|
|
SV_BeginRedirect(RD_CLIENT, host_client->language);
|
|
Info_Print (svs.info, "");
|
|
SV_EndRedirect();
|
|
}
|
|
|
|
void SV_NoSnap_f(void)
|
|
{
|
|
SV_LogPlayer(host_client, "refused snap");
|
|
|
|
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; )
|
|
{
|
|
if (vote->timeout < realtime)
|
|
{
|
|
if (prev)
|
|
prev->next = vote->next;
|
|
else
|
|
voteinfo = vote->next;
|
|
|
|
Z_Free(vote);
|
|
|
|
if (prev)
|
|
vote = prev;
|
|
else
|
|
vote = voteinfo;
|
|
}
|
|
else
|
|
prev = vote;
|
|
|
|
vote = vote->next;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
SV_ClientTPrintf(host_client, PRINT_HIGH, STL_NOVOTING);
|
|
return;
|
|
}
|
|
if (host_client->ismuted)
|
|
{
|
|
SV_ClientTPrintf(host_client, PRINT_HIGH, 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"))
|
|
{
|
|
SV_ClientTPrintf(host_client, PRINT_HIGH, STL_BADVOTE);
|
|
return;
|
|
}
|
|
num = Cmd_Level(command);
|
|
if (base)
|
|
*base = ' ';
|
|
if (num != Cmd_ExecLevel)
|
|
{
|
|
SV_ClientTPrintf(host_client, PRINT_HIGH, 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);
|
|
SV_ClientTPrintf(host_client, PRINT_HIGH, STL_OLDVOTEREMOVED);
|
|
return;
|
|
}
|
|
if (VoteCount(NULL, id)>=3)
|
|
{
|
|
VoteRemoveCommands(NULL, id);
|
|
SV_ClientTPrintf(host_client, PRINT_HIGH, 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);
|
|
Cbuf_AddText("\n", 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);
|
|
}
|
|
}
|
|
|
|
void Cmd_Notarget_f (void)
|
|
{
|
|
if (!SV_MayCheat())
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Cheats are not allowed on this server\n");
|
|
return;
|
|
}
|
|
|
|
SV_LogPlayer(host_client, "notarget cheat");
|
|
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_MayCheat())
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Cheats are not allowed on this server\n");
|
|
return;
|
|
}
|
|
|
|
SV_LogPlayer(host_client, "god cheat");
|
|
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;
|
|
|
|
#ifdef HLSERVER
|
|
if (svs.gametype == GT_HALFLIFE)
|
|
{
|
|
HLSV_ClientCommand(host_client);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (!SV_MayCheat())
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Cheats are not allowed on this server\n");
|
|
return;
|
|
}
|
|
|
|
t = Cmd_Argv(1);
|
|
v = atoi (Cmd_Argv(2));
|
|
|
|
SV_LogPlayer(host_client, "give cheat");
|
|
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;
|
|
default:
|
|
Con_Printf("give: unknown item\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (developer.value < 2 && host_client->netchan.remote_address.type != NA_LOOPBACK) //we don't want clients doing nasty things... like setting movetype 3123
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "'give' debugging command requires developer 2 set on the server before you may use it\n");
|
|
}
|
|
else
|
|
{
|
|
int oldself;
|
|
oldself = pr_global_struct->self;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
|
|
SV_ClientPrintf(host_client, PRINT_HIGH, "Result: %s\n", svprogfuncs->EvaluateDebugString(svprogfuncs, Cmd_Args()));
|
|
pr_global_struct->self = oldself;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Cmd_Noclip_f (void)
|
|
{
|
|
if (!SV_MayCheat())
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Cheats are not allowed on this server\n");
|
|
return;
|
|
}
|
|
|
|
SV_LogPlayer(host_client, "noclip cheat");
|
|
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_MayCheat())
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Cheats are not allowed on this server\n");
|
|
return;
|
|
}
|
|
|
|
SV_LogPlayer(host_client, "fly cheat");
|
|
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 (and added to fte because he kept winging)
|
|
====================
|
|
*/
|
|
void Cmd_SetPos_f(void)
|
|
{
|
|
if (!SV_MayCheat())
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Cheats are not allowed on this server\n");
|
|
return;
|
|
}
|
|
|
|
if (Cmd_Argc() != 4)
|
|
{
|
|
SV_ClientPrintf(host_client, PRINT_HIGH, "setpos %f %f %f\n", sv_player->v->origin[0], sv_player->v->origin[1], sv_player->v->origin[2]);
|
|
return;
|
|
}
|
|
SV_LogPlayer(host_client, "setpos cheat");
|
|
if (sv_player->v->movetype != MOVETYPE_NOCLIP)
|
|
{
|
|
sv_player->v->movetype = MOVETYPE_NOCLIP;
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "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));
|
|
World_LinkEdict (&sv.world, (wedict_t*)sv_player, false);
|
|
}
|
|
|
|
void SV_SetUpClientEdict (client_t *cl, edict_t *ent)
|
|
{
|
|
extern int pr_teamfield;
|
|
#ifdef VM_Q1
|
|
if (svs.gametype == GT_Q1QVM)
|
|
{
|
|
string_t preserve;
|
|
preserve = ent->v->netname;
|
|
Q1QVMED_ClearEdict(ent, true);
|
|
ent->v->netname = preserve;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if (progstype != PROG_NQ) //allow frikbots to work in NQ mods (but not qw!)
|
|
ED_Clear(svprogfuncs, ent);
|
|
ent->v->netname = PR_SetString(svprogfuncs, cl->name);
|
|
}
|
|
ED_Spawned(ent, false);
|
|
ent->isfree = false;
|
|
|
|
ent->v->colormap = NUM_FOR_EDICT(svprogfuncs, ent);
|
|
|
|
if (pr_teamfield)
|
|
((string_t *)ent->v)[pr_teamfield] = (string_t)(cl->team-svprogfuncs->stringtable);
|
|
|
|
{
|
|
int tc = atoi(Info_ValueForKey(cl->userinfo, "topcolor"));
|
|
int bc = atoi(Info_ValueForKey(cl->userinfo, "bottomcolor"));
|
|
if (tc < 0 || tc > 13)
|
|
tc = 0;
|
|
if (bc < 0 || bc > 13)
|
|
bc = 0;
|
|
ent->xv->clientcolors = 16*tc + bc;
|
|
}
|
|
|
|
|
|
ent->xv->gravity = cl->entgravity = 1.0;
|
|
ent->xv->maxspeed = cl->maxspeed = sv_maxspeed.value;
|
|
ent->v->movetype = MOVETYPE_NOCLIP;
|
|
|
|
ent->v->frags = 0;
|
|
cl->connection_started = realtime;
|
|
}
|
|
/*
|
|
==================
|
|
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;
|
|
int seats;
|
|
|
|
if (host_client->controller)
|
|
{
|
|
host_client = host_client->controller;
|
|
sv_player = host_client->edict;
|
|
}
|
|
|
|
if (host_client->state != cs_spawned)
|
|
return;
|
|
|
|
if (svs.gametype != GT_PROGS)
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Sorry, not implemented in this gamecode type. Try moaning at the dev team\n");
|
|
return;
|
|
}
|
|
|
|
if (!(host_client->zquake_extensions & Z_EXT_JOIN_OBSERVE))
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Your QW client doesn't support this command\n");
|
|
return;
|
|
}
|
|
|
|
if (password.string[0] && stricmp(password.string, "none"))
|
|
{
|
|
SV_ClientPrintf(host_client, PRINT_HIGH, "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;
|
|
seats = 0;
|
|
for (i=0,cl=svs.clients ; i<sv.allocated_client_slots ; i++,cl++)
|
|
{
|
|
if (cl->state != cs_free && !cl->spectator)
|
|
numclients++;
|
|
if ((cl == host_client || cl->controller == host_client) && cl->spectator)
|
|
seats++;
|
|
}
|
|
if (numclients+seats > maxclients.value)
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Can't join, all player slots full\n");
|
|
return;
|
|
}
|
|
|
|
for (; host_client; host_client = host_client->controlled)
|
|
{
|
|
sv_player = host_client->edict;
|
|
if (!host_client->spectator)
|
|
continue;
|
|
|
|
// 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);
|
|
sv.spawned_observer_slots--;
|
|
|
|
SV_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
|
|
if (pr_global_ptrs->SetNewParms)
|
|
PR_ExecuteProgram (svprogfuncs, pr_global_struct->SetNewParms);
|
|
for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
|
|
{
|
|
if (pr_global_ptrs->spawnparamglobals[i])
|
|
host_client->spawn_parms[i] = *pr_global_ptrs->spawnparamglobals[i];
|
|
else
|
|
host_client->spawn_parms[i] = 0;
|
|
}
|
|
|
|
// call the spawn function
|
|
pr_global_struct->time = sv.world.physicstime;
|
|
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.world.physicstime;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
|
|
PR_ExecuteProgram (svprogfuncs, pr_global_struct->PutClientInServer);
|
|
sv.spawned_client_slots++;
|
|
|
|
// send notification to all clients
|
|
host_client->old_frags = host_client->edict->v->frags;
|
|
host_client->sendinfo = true;
|
|
|
|
SV_LogPlayer(host_client, "joined");
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
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;
|
|
int seats;
|
|
|
|
if (host_client->controller)
|
|
{
|
|
host_client = host_client->controller;
|
|
sv_player = host_client->edict;
|
|
}
|
|
|
|
if (host_client->state != cs_spawned)
|
|
return;
|
|
|
|
if (svs.gametype != GT_PROGS)
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Sorry, not implemented in this gamecode type. Try moaning at the dev team\n");
|
|
return;
|
|
}
|
|
|
|
if (!(host_client->zquake_extensions & Z_EXT_JOIN_OBSERVE))
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Your QW client doesn't support this command\n");
|
|
return;
|
|
}
|
|
|
|
if (spectator_password.string[0] && stricmp(spectator_password.string, "none"))
|
|
{
|
|
SV_ClientPrintf(host_client, PRINT_HIGH, "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;
|
|
seats = 0;
|
|
for (i=0,cl=svs.clients ; i<sv.allocated_client_slots ; i++,cl++)
|
|
{
|
|
if (cl->state != cs_free && cl->spectator)
|
|
numspectators++;
|
|
if ((cl == host_client || cl->controller == host_client) && !cl->spectator)
|
|
seats++;
|
|
}
|
|
if (numspectators+seats > maxspectators.value)
|
|
{
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Can't join, all spectator slots full\n");
|
|
return;
|
|
}
|
|
|
|
for (; host_client; host_client = host_client->controlled)
|
|
{
|
|
sv_player = host_client->edict;
|
|
if (host_client->spectator)
|
|
continue;
|
|
|
|
// 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);
|
|
sv.spawned_client_slots--;
|
|
|
|
SV_SetUpClientEdict (host_client, host_client->edict);
|
|
|
|
// turn the player into a spectator
|
|
host_client->spectator = true;
|
|
Info_SetValueForStarKey (host_client->userinfo, "*spectator", "1", sizeof(host_client->userinfo));
|
|
|
|
// FIXME, bump the client's userid?
|
|
|
|
// call the progs to get default spawn parms for the new client
|
|
if (pr_global_ptrs->SetNewParms)
|
|
PR_ExecuteProgram (svprogfuncs, pr_global_struct->SetNewParms);
|
|
for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
|
|
{
|
|
if (pr_global_ptrs->spawnparamglobals[i])
|
|
host_client->spawn_parms[i] = *pr_global_ptrs->spawnparamglobals[i];
|
|
else
|
|
host_client->spawn_parms[i] = 0;
|
|
}
|
|
|
|
SV_SpawnSpectator ();
|
|
|
|
// call the spawn function
|
|
if (SpectatorConnect)
|
|
{
|
|
pr_global_struct->time = sv.world.physicstime;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
|
|
PR_ExecuteProgram (svprogfuncs, SpectatorConnect);
|
|
}
|
|
else
|
|
sv_player->v->movetype = MOVETYPE_NOCLIP;
|
|
sv.spawned_observer_slots++;
|
|
|
|
// send notification to all clients
|
|
host_client->old_frags = host_client->edict->v->frags;
|
|
host_client->sendinfo = true;
|
|
|
|
SV_LogPlayer(host_client, "observing");
|
|
}
|
|
}
|
|
|
|
void SV_CalcNetRates(client_t *cl, double *ftime, int *frames, double *minf, double *maxf)
|
|
{
|
|
int f;
|
|
int fmsec;
|
|
*minf = 1000;
|
|
*maxf = 0;
|
|
*ftime = 0;
|
|
*frames = 0;
|
|
|
|
if (ISQWCLIENT(cl) || ISNQCLIENT(cl))
|
|
{
|
|
if (cl->frameunion.frames)
|
|
{
|
|
for (f = 0; f < UPDATE_BACKUP; f++)
|
|
{
|
|
if (cl->frameunion.frames[f].move_msecs >= 0)
|
|
{
|
|
if (!cl->frameunion.frames[f].move_msecs)
|
|
{
|
|
fmsec = 1001;
|
|
}
|
|
else
|
|
{
|
|
fmsec = 1000.0f/cl->frameunion.frames[f].move_msecs;
|
|
}
|
|
*ftime += fmsec;
|
|
if (*minf > fmsec)
|
|
*minf = fmsec;
|
|
if (*maxf < fmsec)
|
|
*maxf = fmsec;
|
|
*frames+=1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Cmd_FPSList_f(void)
|
|
{
|
|
client_t *cl;
|
|
int c;
|
|
double minf, maxf;
|
|
double ftime;
|
|
int frames;
|
|
char *protoname;
|
|
|
|
|
|
for (c = 0; c < sv.allocated_client_slots; c++)
|
|
{
|
|
cl = &svs.clients[c];
|
|
if (!cl->state)
|
|
continue;
|
|
|
|
SV_CalcNetRates(cl, &ftime, &frames, &minf, &maxf);
|
|
|
|
switch(cl->protocol)
|
|
{
|
|
case SCP_QUAKEWORLD: protoname = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"fteqw":(cl->fteprotocolextensions||cl->fteprotocolextensions2?"qw":"qwid"); break;
|
|
case SCP_QUAKE2: protoname = "q2"; break;
|
|
case SCP_QUAKE3: protoname = "q3"; break;
|
|
case SCP_NETQUAKE: protoname = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"ftenq":"nqid"; break;
|
|
case SCP_PROQUAKE: protoname = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"ftenq":"nq"; break;
|
|
case SCP_FITZ666: protoname = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"ftenq":"fitz"; break;
|
|
case SCP_DARKPLACES6: protoname = "dpp6"; break;
|
|
case SCP_DARKPLACES7: protoname = "dpp7"; break;
|
|
default: protoname = "?"; break;
|
|
}
|
|
|
|
if (frames)
|
|
SV_ClientPrintf(host_client, PRINT_HIGH, "%s: %gfps (%g - %g), c2s: %ibps, s2c: %ibps, ping %ims(-%i), pl %i%% %s\n", cl->name, ftime/frames, minf, maxf, (int)cl->inrate, (int)cl->outrate, SV_CalcPing(cl, false), (int)(1000*cl->delay), cl->lossage, protoname);
|
|
else
|
|
SV_ClientPrintf(host_client, PRINT_HIGH, "%s: unknown framerate, c2s: %ibps, s2c: %ibps, ping %ims(-%i), pl %i%% %s\n", cl->name, (int)cl->inrate, (int)cl->outrate, SV_CalcPing(cl, false), (int)(1000*cl->delay), cl->lossage, protoname);
|
|
}
|
|
}
|
|
|
|
void SV_EnableClientsCSQC(void)
|
|
{
|
|
#ifdef PEXT_CSQC
|
|
// if ((host_client->fteprotocolextensions & PEXT_CSQC) || atoi(Cmd_Argv(1)))
|
|
{
|
|
host_client->csqcactive = true;
|
|
if (!host_client->csqcentversions)
|
|
host_client->csqcentversions = Z_Malloc(sizeof(*host_client->csqcentversions) * host_client->max_net_ents);
|
|
if (!host_client->csqcentsequence)
|
|
host_client->csqcentsequence = Z_Malloc(sizeof(*host_client->csqcentsequence) * host_client->max_net_ents);
|
|
}
|
|
// else
|
|
// SV_ClientPrintf(host_client, PRINT_HIGH, "CSQC entities not enabled - no support from network protocol\n");
|
|
#endif
|
|
}
|
|
void SV_DisableClientsCSQC(void)
|
|
{
|
|
#ifdef PEXT_CSQC
|
|
host_client->csqcactive = false;
|
|
#endif
|
|
}
|
|
|
|
void SV_UserCmdMVDList_f (void);
|
|
void SV_STFU_f(void)
|
|
{
|
|
char *msg;
|
|
SV_ClientPrintf(host_client, 255, "stfu\n");
|
|
msg = "cl_antilag 0\n";
|
|
ClientReliableWrite_Begin(host_client, svc_stufftext, 2+strlen(msg));
|
|
ClientReliableWrite_String(host_client, msg);
|
|
}
|
|
|
|
#ifdef NQPROT
|
|
void SVNQ_Spawn_f (void)
|
|
{
|
|
extern cvar_t sv_gravity;
|
|
int i;
|
|
client_t *client;
|
|
edict_t *ent;
|
|
|
|
if (host_client->state != cs_connected)
|
|
{
|
|
Con_Printf ("spawn not valid -- already 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_FullClientUpdate(client, host_client);
|
|
SV_MVD_FullClientUpdate(NULL, host_client);
|
|
|
|
// send all current light styles
|
|
for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
|
|
{
|
|
if (i >= MAX_STANDARDLIGHTSTYLES && host_client->protocol != SCP_DARKPLACES7)
|
|
break; //dp7 clients support more lightstyles.
|
|
|
|
ClientReliableWrite_Begin (host_client, svc_lightstyle,
|
|
3 + (sv.strings.lightstyles[i] ? strlen(sv.strings.lightstyles[i]) : 1));
|
|
ClientReliableWrite_Byte (host_client, (char)i);
|
|
ClientReliableWrite_String (host_client, sv.strings.lightstyles[i]);
|
|
}
|
|
|
|
// set up the edict
|
|
ent = host_client->edict;
|
|
|
|
if (host_client->istobeloaded) //minimal setup
|
|
{
|
|
host_client->entgravity = ent->xv->gravity*sv_gravity.value;
|
|
host_client->maxspeed = ent->xv->maxspeed;
|
|
}
|
|
else
|
|
{
|
|
ED_Clear(svprogfuncs, ent);
|
|
ED_Spawned(ent, false);
|
|
|
|
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->xv->gravity = 1.0;
|
|
host_client->entgravity*=sv_gravity.value;
|
|
host_client->maxspeed = ent->xv->maxspeed = sv_maxspeed.value;
|
|
}
|
|
|
|
//
|
|
// force stats to be updated
|
|
//
|
|
memset (host_client->statsi, 0, sizeof(host_client->statsi));
|
|
memset (host_client->statsf, 0, sizeof(host_client->statsf));
|
|
memset (host_client->statss, 0, sizeof(host_client->statss));
|
|
|
|
ClientReliableWrite_Begin (host_client, svcnq_updatestatlong, 6);
|
|
ClientReliableWrite_Byte (host_client, STAT_TOTALSECRETS);
|
|
ClientReliableWrite_Long (host_client, pr_global_struct->total_secrets);
|
|
|
|
ClientReliableWrite_Begin (host_client, svcnq_updatestatlong, 6);
|
|
ClientReliableWrite_Byte (host_client, STAT_TOTALMONSTERS);
|
|
ClientReliableWrite_Long (host_client, pr_global_struct->total_monsters);
|
|
|
|
ClientReliableWrite_Begin (host_client, svcnq_updatestatlong, 6);
|
|
ClientReliableWrite_Byte (host_client, STAT_SECRETS);
|
|
ClientReliableWrite_Long (host_client, pr_global_struct->found_secrets);
|
|
|
|
ClientReliableWrite_Begin (host_client, svcnq_updatestatlong, 6);
|
|
ClientReliableWrite_Byte (host_client, STAT_MONSTERS);
|
|
ClientReliableWrite_Long (host_client, pr_global_struct->killed_monsters);
|
|
|
|
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++)
|
|
{
|
|
if (pr_global_ptrs->spawnparamglobals[i])
|
|
*pr_global_ptrs->spawnparamglobals[i] = host_client->spawn_parms[i];
|
|
}
|
|
|
|
// call the spawn function
|
|
pr_global_struct->time = sv.world.physicstime;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
|
|
PR_ExecuteProgram (svprogfuncs, SpectatorConnect);
|
|
}
|
|
sv.spawned_observer_slots++;
|
|
}
|
|
else
|
|
{
|
|
sv.spawned_client_slots++;
|
|
|
|
// copy spawn parms out of the client_t
|
|
for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
|
|
{
|
|
if (pr_global_ptrs->spawnparamglobals[i])
|
|
*pr_global_ptrs->spawnparamglobals[i] = host_client->spawn_parms[i];
|
|
}
|
|
|
|
// call the spawn function
|
|
pr_global_struct->time = sv.world.physicstime;
|
|
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.world.physicstime;
|
|
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_BroadcastPrintf (PRINT_HIGH, "warning: %s eyes or player model not verified\n", host_client->name);
|
|
}
|
|
|
|
|
|
// if we are paused, tell the client
|
|
if (sv.paused)
|
|
{
|
|
if (!ISQ2CLIENT(host_client))
|
|
{
|
|
ClientReliableWrite_Begin (host_client, svc_setpause, 2);
|
|
ClientReliableWrite_Byte (host_client, sv.paused!=0);
|
|
}
|
|
if (sv.paused&~4)
|
|
SV_ClientTPrintf(host_client, PRINT_HIGH, STL_SERVERPAUSED!=0);
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
if (host_client->prespawn_stage == PRESPAWN_MAPCHECK)
|
|
{
|
|
host_client->checksum = ~0u;
|
|
host_client->prespawn_stage = PRESPAWN_MAPCHECK+1;
|
|
host_client->prespawn_idx = 0;
|
|
}
|
|
|
|
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), sizeof(host_client->userinfo));
|
|
Info_SetValueForKey(host_client->userinfo, "bottomcolor", va("%i", bottom), sizeof(host_client->userinfo));
|
|
switch(bottom)
|
|
{
|
|
case 4:
|
|
Info_SetValueForKey(host_client->userinfo, "team", "red", sizeof(host_client->userinfo));
|
|
break;
|
|
case 14:
|
|
Info_SetValueForKey(host_client->userinfo, "team", "blue", sizeof(host_client->userinfo));
|
|
break;
|
|
default:
|
|
Info_SetValueForKey(host_client->userinfo, "team", va("t%i", bottom+1), sizeof(host_client->userinfo));
|
|
break;
|
|
}
|
|
|
|
SV_ExtractFromUserinfo (host_client, true);
|
|
|
|
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);
|
|
}
|
|
|
|
void SVNQ_Ping_f(void)
|
|
{
|
|
int i;
|
|
client_t *cl;
|
|
|
|
SV_PrintToClient(host_client, PRINT_HIGH, "Client ping times:\n");
|
|
for (i=0,cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++)
|
|
{
|
|
if (!cl->state)
|
|
continue;
|
|
|
|
SV_PrintToClient(host_client, PRINT_HIGH, va("%3i %s\n", SV_CalcPing (cl, false), cl->name));
|
|
}
|
|
}
|
|
|
|
void SV_Pext_f(void)
|
|
{
|
|
int i;
|
|
char *tag;
|
|
char *val;
|
|
|
|
if (host_client->pextknown)
|
|
return;
|
|
host_client->pextknown = true;
|
|
|
|
for (i = 1; i < Cmd_Argc(); )
|
|
{
|
|
tag = Cmd_Argv(i++);
|
|
val = Cmd_Argv(i++);
|
|
switch(strtoul(tag, NULL, 0))
|
|
{
|
|
case PROTOCOL_VERSION_FTE:
|
|
host_client->fteprotocolextensions = strtoul(val, NULL, 0) & Net_PextMask(1, ISNQCLIENT(host_client));
|
|
break;
|
|
case PROTOCOL_VERSION_FTE2:
|
|
host_client->fteprotocolextensions2 = strtoul(val, NULL, 0) & Net_PextMask(2, ISNQCLIENT(host_client));
|
|
break;
|
|
}
|
|
}
|
|
|
|
Con_DPrintf("%s now using pext: %x, %x\n", host_client->name, host_client->fteprotocolextensions, host_client->fteprotocolextensions2);
|
|
|
|
SV_ClientProtocolExtensionsChanged(host_client);
|
|
|
|
if (ISNQCLIENT(host_client))
|
|
SVNQ_New_f();
|
|
else
|
|
SV_New_f();
|
|
}
|
|
|
|
/*
|
|
void SVNQ_ExecuteUserCommand (char *s)
|
|
{
|
|
client_t *oldhost = host_client;
|
|
ucmd_t *u;
|
|
|
|
Cmd_TokenizeString (s, false, false);
|
|
sv_player = host_client->edict;
|
|
|
|
Cmd_ExecLevel=1;
|
|
|
|
for (u=nqucmds ; u->name ; u++)
|
|
{
|
|
if (!strcmp (Cmd_Argv(0), u->name) )
|
|
{
|
|
if (/ *!fromQC && * /!u->noqchandling)
|
|
if (PR_UserCmd(s))
|
|
{
|
|
host_client = oldhost;
|
|
return;
|
|
}
|
|
|
|
if (!u->func)
|
|
{
|
|
SV_BeginRedirect (RD_CLIENT, host_client->language);
|
|
Con_Printf("Command was disabled\n");
|
|
SV_EndRedirect ();
|
|
}
|
|
else
|
|
{
|
|
SV_BeginRedirect (RD_CLIENT, host_client->language);
|
|
u->func ();
|
|
SV_EndRedirect ();
|
|
}
|
|
|
|
host_client = oldhost;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!u->name)
|
|
Con_Printf("%s tried to \"%s\"\n", host_client->name, s);
|
|
}
|
|
*/
|
|
#endif
|
|
|
|
|
|
void SV_MVDList_f (void);
|
|
void SV_MVDInfo_f (void);
|
|
typedef struct
|
|
{
|
|
char *name;
|
|
void (*func) (void);
|
|
qboolean noqchandling;
|
|
} ucmd_t;
|
|
|
|
ucmd_t ucmds[] =
|
|
{
|
|
/*connection process*/
|
|
{"new", SV_New_f, true},
|
|
{"modellist", SVQW_Modellist_f, true},
|
|
{"soundlist", SVQW_Soundlist_f, true},
|
|
{"prespawn", SVQW_PreSpawn_f, true},
|
|
{"spawn", SVQW_Spawn_f, true},
|
|
{"begin", SV_Begin_f, true},
|
|
|
|
/*ezquake warning*/
|
|
{"al", SV_STFU_f, true},
|
|
|
|
{"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},
|
|
|
|
/*demo/download commands*/
|
|
{"stopdownload", SV_StopDownload_f},
|
|
{"demolist", SV_UserCmdMVDList_f},
|
|
{"demoinfo", SV_MVDInfo_f},
|
|
{"dlsize", SV_DownloadSize_f},
|
|
{"download", SV_BeginDownload_f},
|
|
{"nextdl", SV_NextDownload_f},
|
|
|
|
/*quakeworld specific things*/
|
|
{"join", Cmd_Join_f},
|
|
{"observe", Cmd_Observe_f},
|
|
{"snap", SV_NoSnap_f},
|
|
{"ptrack", SV_PTrack_f}, //ZOID - used with autocam
|
|
|
|
{"enablecsqc", SV_EnableClientsCSQC},
|
|
{"disablecsqc", SV_DisableClientsCSQC},
|
|
|
|
{"vote", SV_Vote_f},
|
|
|
|
#ifdef SVRANKING
|
|
{"topten", Rank_ListTop10_f},
|
|
#endif
|
|
|
|
{"efpslist", Cmd_FPSList_f}, //don't conflict with the ktpro one
|
|
{"god", Cmd_God_f},
|
|
{"give", Cmd_Give_f},
|
|
{"noclip", Cmd_Noclip_f},
|
|
{"fly", Cmd_Fly_f},
|
|
{"notarget", Cmd_Notarget_f},
|
|
{"setpos", Cmd_SetPos_f},
|
|
|
|
#ifdef NQPROT
|
|
{"name", SVNQ_NQInfo_f},
|
|
#endif
|
|
|
|
#ifdef VOICECHAT
|
|
{"voicetarg", SV_Voice_Target_f},
|
|
{"vignore", SV_Voice_Ignore_f}, /*ignore/mute specific player*/
|
|
{"muteall", SV_Voice_MuteAll_f}, /*disables*/
|
|
{"unmuteall", SV_Voice_UnmuteAll_f}, /*reenables*/
|
|
#endif
|
|
|
|
{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},
|
|
{"info", 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
|
|
|
|
#ifdef NQPROT
|
|
ucmd_t nqucmds[] =
|
|
{
|
|
{"new", SVNQ_New_f, true},
|
|
{"spawn", SVNQ_Spawn_f, true},
|
|
{"begin", SVNQ_Begin_f, true},
|
|
{"prespawn", SVNQ_PreSpawn_f, true},
|
|
|
|
{"status", NULL},
|
|
|
|
|
|
{"god", Cmd_God_f},
|
|
{"give", Cmd_Give_f},
|
|
{"notarget", Cmd_Notarget_f},
|
|
{"fly", Cmd_Fly_f},
|
|
{"noclip", Cmd_Noclip_f},
|
|
|
|
{"say", SV_Say_f},
|
|
{"say_team", SV_Say_Team_f},
|
|
{"tell", SV_SayOne_f},
|
|
{"efpslist", Cmd_FPSList_f}, //don't conflict with the ktpro one
|
|
|
|
{"pings", SV_Pings_f},
|
|
{"ping", SVNQ_Ping_f},
|
|
|
|
{"kill", SV_Kill_f},
|
|
{"pause", SV_Pause_f},
|
|
{"kick", NULL},
|
|
{"ban", NULL},
|
|
{"vote", SV_Vote_f},
|
|
|
|
/*DP download protocol*/
|
|
{"dlsize", SV_DownloadSize_f},
|
|
{"download", SV_BeginDownload_f},
|
|
{"sv_startdownload", SVDP_StartDownload_f},
|
|
|
|
/*userinfo stuff*/
|
|
{"setinfo", SV_SetInfo_f},
|
|
{"name", SVNQ_NQInfo_f},
|
|
{"color", SVNQ_NQColour_f},
|
|
{"playermodel", NULL},
|
|
{"playerskin", NULL},
|
|
{"rate", SV_Rate_f},
|
|
|
|
#ifdef SVRANKING
|
|
{"topten", Rank_ListTop10_f},
|
|
#endif
|
|
|
|
/*various misc extensions*/
|
|
{"pext", SV_Pext_f},
|
|
{"enablecsqc", SV_EnableClientsCSQC},
|
|
{"disablecsqc", SV_DisableClientsCSQC},
|
|
{"challengeconnect", NULL},
|
|
|
|
#ifdef VOICECHAT
|
|
{"voicetarg", SV_Voice_Target_f},
|
|
{"vignore", SV_Voice_Ignore_f}, /*ignore/mute specific player*/
|
|
{"muteall", SV_Voice_MuteAll_f}, /*disables*/
|
|
{"unmuteall", SV_Voice_UnmuteAll_f}, /*reenables*/
|
|
#endif
|
|
|
|
{NULL, NULL}
|
|
};
|
|
#endif
|
|
|
|
/*
|
|
==================
|
|
SV_ExecuteUserCommand
|
|
==================
|
|
*/
|
|
void SV_ExecuteUserCommand (char *s, qboolean fromQC)
|
|
{
|
|
ucmd_t *u;
|
|
client_t *oldhost = host_client;
|
|
char adr[MAX_ADR_SIZE];
|
|
|
|
Con_DPrintf("Client command: %s\n", s);
|
|
|
|
Cmd_TokenizeString (s, false, false);
|
|
sv_player = host_client->edict;
|
|
|
|
Cmd_ExecLevel=1;
|
|
|
|
if (host_client->controlled && 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 (ISQ2CLIENT(host_client))
|
|
u = ucmdsq2;
|
|
else
|
|
#endif
|
|
#ifdef NQPROT
|
|
if (ISNQCLIENT(host_client))
|
|
u = nqucmds;
|
|
else
|
|
#endif
|
|
u=ucmds;
|
|
|
|
for ( ; u->name ; u++)
|
|
if (!strcmp (Cmd_Argv(0), u->name) )
|
|
{
|
|
if (!fromQC && !u->noqchandling)
|
|
if (PR_KrimzonParseCommand(s)) //KRIMZON_SV_PARSECLIENTCOMMAND has the opertunity to parse out certain commands.
|
|
{
|
|
host_client = oldhost;
|
|
return;
|
|
}
|
|
// SV_BeginRedirect (RD_CLIENT, host_client->language);
|
|
if (u->func)
|
|
u->func ();
|
|
host_client = oldhost;
|
|
// SV_EndRedirect ();
|
|
return;
|
|
}
|
|
|
|
if (!u->name)
|
|
{
|
|
#ifdef HLSERVER
|
|
if (HLSV_ClientCommand(host_client))
|
|
{
|
|
host_client = oldhost;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (!fromQC)
|
|
if (PR_UserCmd(s)) //Q2 and MVDSV command handling only happens if the engine didn't recognise it.
|
|
{
|
|
host_client = oldhost;
|
|
return;
|
|
}
|
|
#ifdef SVRANKING
|
|
if (sv_cmdlikercon.value && host_client->rankid)
|
|
{
|
|
char remaining[1024];
|
|
int i;
|
|
rankstats_t stats;
|
|
|
|
if (!Rank_GetPlayerStats(host_client->rankid, &stats))
|
|
{
|
|
host_client = oldhost;
|
|
return;
|
|
}
|
|
|
|
Con_Printf ("cmd from %s:\n%s\n"
|
|
, host_client->name, net_message.data+4);
|
|
|
|
SV_BeginRedirect (RD_CLIENT, host_client->language);
|
|
|
|
remaining[0] = 0;
|
|
|
|
for (i=0 ; i<Cmd_Argc() ; i++)
|
|
{
|
|
if (strlen(remaining)+strlen(Cmd_Argv(i))>=sizeof(remaining)-1)
|
|
{
|
|
Con_Printf("cmd was too long\n");
|
|
host_client = oldhost;
|
|
SV_EndRedirect ();
|
|
Con_Printf ("cmd from %s:\n%s\n"
|
|
, NET_AdrToString (adr, sizeof(adr), &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;
|
|
}
|
|
#endif
|
|
Con_Printf ("Bad user command: %s\n", Cmd_Argv(0));
|
|
}
|
|
|
|
host_client = oldhost;
|
|
SV_EndRedirect ();
|
|
}
|
|
|
|
int implevels[256];
|
|
qboolean SV_FilterImpulse(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_DPrintf("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 ( edict_t *player, 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;
|
|
|
|
model_t *model;
|
|
|
|
pl = EDICT_TO_PROG(svprogfuncs, player);
|
|
|
|
// touch linked edicts
|
|
for (l = node->edicts.next ; l != &node->edicts ; l = next)
|
|
{
|
|
next = l->next;
|
|
check = (edict_t*)EDICT_FROM_AREA(l);
|
|
|
|
if (check->v->owner == pl)
|
|
continue; // player's own missile
|
|
if (check == player)
|
|
continue;
|
|
if ((check->v->solid == SOLID_TRIGGER && check->v->skin < 0) || check->v->solid == SOLID_BSP
|
|
|| check->v->solid == SOLID_BBOX
|
|
|| check->v->solid == SOLID_SLIDEBOX
|
|
//|| (check->v->solid == SOLID_PHASEH2 && progstype == PROG_H2) //logically matches hexen2, but I hate it
|
|
)
|
|
{
|
|
|
|
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)player->xv->dimension_solid & (int)check->xv->dimension_hit);
|
|
if (!((int)player->xv->dimension_hit & (int)check->xv->dimension_solid))
|
|
continue;
|
|
if (!check->v->size[0]) //points are not meant to be solid
|
|
continue;
|
|
pmove.numphysent++;
|
|
|
|
VectorCopy (check->v->origin, pe->origin);
|
|
pe->info = NUM_FOR_EDICT(svprogfuncs, check);
|
|
pe->nonsolid = check->v->solid == SOLID_TRIGGER;
|
|
switch((int)check->v->skin)
|
|
{
|
|
case Q1CONTENTS_WATER:
|
|
pe->forcecontentsmask = FTECONTENTS_WATER;
|
|
break;
|
|
case Q1CONTENTS_LAVA:
|
|
pe->forcecontentsmask = FTECONTENTS_LAVA;
|
|
break;
|
|
case Q1CONTENTS_SLIME:
|
|
pe->forcecontentsmask = FTECONTENTS_SLIME;
|
|
break;
|
|
case Q1CONTENTS_SKY:
|
|
pe->forcecontentsmask = FTECONTENTS_SKY;
|
|
break;
|
|
case Q1CONTENTS_LADDER:
|
|
pe->forcecontentsmask = FTECONTENTS_LADDER;
|
|
break;
|
|
default:
|
|
pe->forcecontentsmask = 0;
|
|
break;
|
|
}
|
|
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)];
|
|
VectorCopy (check->v->angles, pe->angles);
|
|
}
|
|
else
|
|
{
|
|
pe->model = NULL;
|
|
VectorCopy (check->v->mins, pe->mins);
|
|
VectorCopy (check->v->maxs, pe->maxs);
|
|
VectorClear (pe->angles);
|
|
}
|
|
}
|
|
}
|
|
if (player->v->mins[2] != 24) //crouching/dead
|
|
for (l = node->edicts.next ; l != &node->edicts ; l = next)
|
|
{
|
|
next = l->next;
|
|
check = (edict_t*)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)player->xv->dimension_hit & (int)check->xv->dimension_solid))
|
|
continue;
|
|
|
|
model = sv.models[(int)check->v->modelindex];
|
|
if (model)
|
|
{
|
|
vec3_t axis[3];
|
|
AngleVectors(check->v->angles, axis[0], axis[1], axis[2]);
|
|
VectorNegate(axis[1], axis[1]);
|
|
// test the point
|
|
if (model->funcs.PointContents (model, axis, player->v->origin) == FTECONTENTS_SOLID)
|
|
player->xv->pmove_flags = (int)player->xv->pmove_flags | PMF_LADDER; //touch that ladder!
|
|
}
|
|
}
|
|
}
|
|
|
|
// recurse down both sides
|
|
if (node->axis == -1)
|
|
return;
|
|
|
|
if (pmove_maxs[node->axis] > node->dist)
|
|
AddLinksToPmove (player, node->children[0]);
|
|
if (pmove_mins[node->axis] < node->dist)
|
|
AddLinksToPmove (player, 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.world.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);
|
|
pmove.physents[pmove.numphysent].info = e;
|
|
if (check->v->solid == SOLID_BSP)
|
|
{
|
|
VectorCopy (check->v->angles, pe->angles);
|
|
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)
|
|
{
|
|
#ifdef SERVER_DEMO_PLAYBACK
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
if (sv_brokenmovetypes.value) //this is to mimic standard qw servers, which don't support movetypes other than MOVETYPE_FLY.
|
|
{ //it prevents bugs from being visible in unsuspecting mods.
|
|
if (cl->spectator)
|
|
{
|
|
if (cl->zquake_extensions & Z_EXT_PM_TYPE_NEW)
|
|
return PM_SPECTATOR;
|
|
return PM_OLD_SPECTATOR;
|
|
}
|
|
|
|
if (cl->edict->v->health <= 0)
|
|
return PM_DEAD;
|
|
return PM_NORMAL;
|
|
}
|
|
|
|
switch((int)cl->edict->v->movetype)
|
|
{
|
|
case MOVETYPE_NOCLIP:
|
|
/*older/vanilla clients have a b0rked spectator mode that we don't want to break*/
|
|
if (cl->zquake_extensions & Z_EXT_PM_TYPE_NEW)
|
|
return PM_SPECTATOR;
|
|
return PM_OLD_SPECTATOR;
|
|
|
|
case MOVETYPE_WALLWALK:
|
|
return PM_WALLWALK;
|
|
|
|
case MOVETYPE_FLY:
|
|
return PM_FLY;
|
|
|
|
case MOVETYPE_NONE:
|
|
return PM_NONE;
|
|
|
|
case MOVETYPE_WALK:
|
|
default:
|
|
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;
|
|
char adr[MAX_ADR_SIZE];
|
|
vec3_t new_vel;
|
|
vec3_t old_vel;
|
|
|
|
// 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)
|
|
{
|
|
if (!host_client->last_check)
|
|
{
|
|
host_client->msecs = 0;
|
|
host_client->last_check = realtime;
|
|
}
|
|
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(adr, sizeof(adr), &host_client->netchan.remote_address));
|
|
host_client->drop = true; //drop later
|
|
}
|
|
}
|
|
|
|
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...
|
|
|
|
if (SV_RunFullQCMovement(host_client, ucmd))
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
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 their msec.
|
|
cmd.impulse = 0;
|
|
SV_RunCmd (&cmd, true);
|
|
return;
|
|
}
|
|
|
|
host_frametime = ucmd->msec * 0.001;
|
|
host_frametime *= sv.gamespeed;
|
|
if (host_frametime > 0.1)
|
|
host_frametime = 0.1;
|
|
|
|
#ifdef SERVER_DEMO_PLAYBACK
|
|
if (sv.demostatevalid)
|
|
{ //spectators watching MVDs do not affect the running progs.
|
|
player_mins[0] = -16;
|
|
player_mins[1] = -16;
|
|
player_mins[2] = -24;
|
|
|
|
player_maxs[0] = 16;
|
|
player_maxs[1] = 16;
|
|
player_maxs[2] = 32;
|
|
|
|
pmove.angles[0] = SHORT2ANGLE(ucmd->angles[0]);
|
|
pmove.angles[1] = SHORT2ANGLE(ucmd->angles[1]);
|
|
pmove.angles[2] = SHORT2ANGLE(ucmd->angles[2]);
|
|
|
|
VectorCopy (host_client->specorigin, pmove.origin);
|
|
VectorCopy (host_client->specvelocity, pmove.velocity);
|
|
|
|
if (host_client->zquake_extensions & Z_EXT_PM_TYPE_NEW)
|
|
pmove.pm_type = PM_SPECTATOR;
|
|
else
|
|
pmove.pm_type = PM_OLD_SPECTATOR;
|
|
pmove.jump_held = host_client->jump_held;
|
|
pmove.jump_msec = 0;
|
|
pmove.waterjumptime = 0;
|
|
pmove.numphysent = 1;
|
|
pmove.physents[0].model = sv.world.worldmodel;
|
|
pmove.cmd = *ucmd;
|
|
pmove.hullnum = SV_HullNumForPlayer(0, player_mins, player_maxs);
|
|
|
|
movevars.entgravity = 0;
|
|
movevars.maxspeed = 0;
|
|
movevars.bunnyspeedcap = pm_bunnyspeedcap.value;
|
|
movevars.ktjump = pm_ktjump.value;
|
|
movevars.slidefix = (pm_slidefix.value != 0);
|
|
movevars.airstep = (pm_airstep.value != 0);
|
|
movevars.walljump = (pm_walljump.value);
|
|
movevars.slidyslopes = (pm_slidyslopes.value!=0);
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
pmove_mins[i] = pmove.origin[i] - 256;
|
|
pmove_maxs[i] = pmove.origin[i] + 256;
|
|
}
|
|
|
|
PM_PlayerMove (sv.gamespeed);
|
|
|
|
VectorCopy (pmove.origin, host_client->specorigin);
|
|
VectorCopy (pmove.velocity, host_client->specvelocity);
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#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->xv->light_level = 128; //hmm... HACK!!!
|
|
|
|
sv_player->v->button0 = ucmd->buttons & 1;
|
|
sv_player->v->button2 = (ucmd->buttons >> 1) & 1;
|
|
if (pr_allowbutton1.ival) //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->xv->button3 = ((ucmd->buttons >> 2) & 1);
|
|
sv_player->xv->button4 = ((ucmd->buttons >> 3) & 1);
|
|
sv_player->xv->button5 = ((ucmd->buttons >> 4) & 1);
|
|
sv_player->xv->button6 = ((ucmd->buttons >> 5) & 1);
|
|
sv_player->xv->button7 = ((ucmd->buttons >> 6) & 1);
|
|
sv_player->xv->button8 = ((ucmd->buttons >> 7) & 1);
|
|
if (ucmd->impulse && SV_FilterImpulse(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;
|
|
}
|
|
|
|
if (host_client->state && host_client->protocol != SCP_BAD)
|
|
{
|
|
sv_player->xv->movement[0] = ucmd->forwardmove * host_frametime;
|
|
sv_player->xv->movement[1] = ucmd->sidemove * host_frametime;
|
|
sv_player->xv->movement[2] = ucmd->upmove * host_frametime;
|
|
}
|
|
|
|
WPhys_CheckVelocity(&sv.world, (wedict_t*)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 (SV_PlayerPhysicsQC && !host_client->spectator)
|
|
{ //player movement tweaks that fuck over player prediction.
|
|
pr_global_struct->frametime = host_frametime;
|
|
pr_global_struct->time = sv.time;
|
|
WPhys_RunEntity(&sv.world, (wedict_t*)sv_player);
|
|
return;
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
jumpable = ((int)sv_player->v->flags & FL_JUMPRELEASED) && ((int)sv_player->v->flags & FL_ONGROUND);
|
|
|
|
pmove.waterjumptime = sv_player->v->teleport_time;
|
|
if (pmove.waterjumptime > sv.time)
|
|
sv_player->v->flags = (int)sv_player->v->flags | FL_WATERJUMP;
|
|
}
|
|
else
|
|
jumpable = false;
|
|
|
|
#ifdef VM_Q1
|
|
if (svs.gametype == GT_Q1QVM)
|
|
Q1QVM_PlayerPreThink();
|
|
else
|
|
#endif
|
|
if (pr_global_ptrs->PlayerPreThink)
|
|
PR_ExecuteProgram (svprogfuncs, *pr_global_ptrs->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;
|
|
}
|
|
|
|
WPhys_RunThink (&sv.world, (wedict_t*)sv_player);
|
|
|
|
if (host_client->state && host_client->protocol == SCP_BAD)
|
|
{
|
|
//botclients update their movement during prethink. make sure we use that stuff.
|
|
ucmd->angles[0] = (int)(sv_player->v->v_angle[0] * (65535/360.0f));
|
|
ucmd->angles[1] = (int)(sv_player->v->v_angle[1] * (65535/360.0f));
|
|
ucmd->angles[2] = (int)(sv_player->v->v_angle[2] * (65535/360.0f));
|
|
|
|
if (host_frametime)
|
|
{
|
|
ucmd->forwardmove = sv_player->xv->movement[0] / host_frametime;
|
|
ucmd->sidemove = sv_player->xv->movement[1] / host_frametime;
|
|
ucmd->upmove = sv_player->xv->movement[2] / host_frametime;
|
|
}
|
|
else
|
|
{
|
|
ucmd->forwardmove = 0;
|
|
ucmd->sidemove = 0;
|
|
ucmd->upmove = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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];
|
|
|
|
VectorCopy(sv_player->xv->gravitydir, pmove.gravitydir);
|
|
|
|
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.world.worldmodel;
|
|
pmove.cmd = *ucmd;
|
|
pmove.skipent = -1;
|
|
|
|
movevars.entgravity = host_client->entgravity/movevars.gravity;
|
|
movevars.maxspeed = host_client->maxspeed;
|
|
movevars.bunnyspeedcap = pm_bunnyspeedcap.value;
|
|
movevars.ktjump = pm_ktjump.value;
|
|
movevars.slidefix = (pm_slidefix.value != 0);
|
|
movevars.airstep = (pm_airstep.value != 0);
|
|
movevars.walljump = (pm_walljump.value);
|
|
movevars.slidyslopes = (pm_slidyslopes.value!=0);
|
|
|
|
if (sv_player->xv->hasted)
|
|
movevars.maxspeed*=sv_player->xv->hasted;
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
pmove_mins[i] = pmove.origin[i] - 256;
|
|
pmove_maxs[i] = pmove.origin[i] + 256;
|
|
}
|
|
sv_player->xv->pmove_flags = (int)sv_player->xv->pmove_flags & ~PMF_LADDER; //assume not touching ladder trigger
|
|
#if 1
|
|
AddLinksToPmove ( sv_player, sv.world.areanodes );
|
|
#else
|
|
AddAllEntsToPmove ();
|
|
#endif
|
|
|
|
if ((int)sv_player->xv->pmove_flags & PMF_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 (sv.gamespeed);
|
|
#endif
|
|
|
|
host_client->jump_held = pmove.jump_held;
|
|
if (progstype != PROG_QW) //this is just annoying.
|
|
{
|
|
if (pmove.waterjumptime)
|
|
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;
|
|
|
|
VectorCopy (pmove.origin, sv_player->v->origin);
|
|
VectorCopy (pmove.angles, sv_player->v->v_angle);
|
|
|
|
if (pmove.gravitydir[0] || pmove.gravitydir[1] || pmove.gravitydir[2] != -1)
|
|
{
|
|
if (!sv_player->v->fixangle)
|
|
{
|
|
//FIXME: bound to pmove.gravitydir
|
|
vec3_t view[3];
|
|
vec3_t surf[3];
|
|
vec3_t fwd, up;
|
|
AngleVectors(sv_player->v->v_angle, view[0], view[1], view[2]);
|
|
/*calculate the surface axis with up from the pmove code and right/forwards relative to the player's directions*/
|
|
VectorNegate(pmove.gravitydir, surf[2]);
|
|
CrossProduct(view[0], surf[2], surf[1]);
|
|
VectorNormalize(surf[1]);
|
|
CrossProduct(surf[2], surf[1], surf[0]);
|
|
/*interpolate the forward direction to be 1/3rd the player, and 2/3rds the surface forward*/
|
|
VectorInterpolate(surf[0], 0.333, view[0], fwd);
|
|
CrossProduct(surf[1], fwd, up);
|
|
/*we have our player's new axis*/
|
|
VectorAngles(fwd, up, sv_player->v->angles);
|
|
}
|
|
}
|
|
|
|
player_mins[0] = -16;
|
|
player_mins[1] = -16;
|
|
player_mins[2] = -24;
|
|
|
|
player_maxs[0] = 16;
|
|
player_maxs[1] = 16;
|
|
player_maxs[2] = 32;
|
|
|
|
VectorCopy(sv_player->v->velocity, old_vel);
|
|
VectorCopy(pmove.velocity, new_vel);
|
|
if (progstype == PROG_QW)
|
|
VectorCopy(new_vel, sv_player->v->velocity);
|
|
|
|
|
|
if (!host_client->spectator)
|
|
{
|
|
// link into place and touch triggers
|
|
World_LinkEdict (&sv.world, (wedict_t*)sv_player, true);
|
|
|
|
/* for (i = 0; i < pmove.numphysent; i++)
|
|
{
|
|
|
|
}
|
|
*/
|
|
// touch other objects
|
|
for (i=0 ; i<pmove.numtouch ; i++)
|
|
{
|
|
if (sv_pushplayers.value)
|
|
{
|
|
n = pmove.physents[pmove.touchindex[i]].info;
|
|
if (n && n <= sv.allocated_client_slots)
|
|
{
|
|
float vel;
|
|
vec3_t dir;
|
|
vec3_t svel;
|
|
ent = EDICT_NUM(svprogfuncs, n);
|
|
VectorSubtract(ent->v->origin, sv_player->v->origin, dir);
|
|
VectorNormalize(dir);
|
|
VectorCopy(sv_player->v->velocity, svel);
|
|
VectorNormalize(svel);
|
|
vel = DotProduct(svel, dir);
|
|
VectorMA(ent->v->velocity, sv_pushplayers.value*vel, dir, ent->v->velocity);
|
|
}
|
|
}
|
|
|
|
|
|
if (pmove.physents[pmove.touchindex[i]].notouch)
|
|
continue;
|
|
n = pmove.physents[pmove.touchindex[i]].info;
|
|
ent = EDICT_NUM(svprogfuncs, n);
|
|
if (playertouch[n/8]&(1<<(n%8)))
|
|
continue;
|
|
|
|
if (ent->v->touch)
|
|
{
|
|
if (progstype != PROG_QW && VectorCompare(sv_player->v->velocity, old_vel))
|
|
{
|
|
VectorCopy(pmove.touchvel[i], old_vel);
|
|
VectorCopy(pmove.touchvel[i], sv_player->v->velocity);
|
|
}
|
|
sv.world.Event_Touch(&sv.world, (wedict_t*)ent, (wedict_t*)sv_player);
|
|
}
|
|
playertouch[n/8] |= 1 << (n%8);
|
|
|
|
if (sv_player->v->touch && !ent->isfree)
|
|
sv.world.Event_Touch(&sv.world, (wedict_t*)sv_player, (wedict_t*)ent);
|
|
}
|
|
}
|
|
|
|
if (progstype != PROG_QW)
|
|
{
|
|
if (VectorCompare(sv_player->v->velocity, old_vel))
|
|
VectorCopy(new_vel, sv_player->v->velocity);
|
|
}
|
|
}
|
|
|
|
/*
|
|
===========
|
|
SV_PostRunCmd
|
|
===========
|
|
Done after running a player command.
|
|
*/
|
|
void SV_PostRunCmd(void)
|
|
{
|
|
// run post-think
|
|
|
|
if (!svprogfuncs)
|
|
return;
|
|
|
|
#ifdef VM_Q1
|
|
if (svs.gametype == GT_Q1QVM)
|
|
{
|
|
pr_global_struct->time = sv.time;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
|
|
Q1QVM_PostThink();
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if (!host_client->spectator)
|
|
{
|
|
pr_global_struct->time = sv.time;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
|
|
|
|
if (pr_global_ptrs->PlayerPostThink)
|
|
PR_ExecuteProgram (svprogfuncs, *pr_global_ptrs->PlayerPostThink);
|
|
|
|
WPhys_RunNewmis (&sv.world);
|
|
}
|
|
else if (SpectatorThink)
|
|
{
|
|
pr_global_struct->time = sv.time;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
|
|
PR_ExecuteProgram (svprogfuncs, SpectatorThink);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SV_ReadPrydonCursor(void)
|
|
{
|
|
float f;
|
|
int entnum;
|
|
eval_t *cursor_screen, *cursor_start, *cursor_impact, *cursor_entitynumber;
|
|
|
|
if (svprogfuncs)
|
|
{
|
|
cursor_screen = svprogfuncs->GetEdictFieldValue(svprogfuncs, host_client->edict, "cursor_screen", NULL);
|
|
cursor_start = svprogfuncs->GetEdictFieldValue(svprogfuncs, host_client->edict, "cursor_trace_start", NULL);
|
|
cursor_impact = svprogfuncs->GetEdictFieldValue(svprogfuncs, host_client->edict, "cursor_trace_endpos", NULL);
|
|
cursor_entitynumber = svprogfuncs->GetEdictFieldValue(svprogfuncs, host_client->edict, "cursor_trace_ent", NULL);
|
|
}
|
|
else
|
|
{
|
|
cursor_screen = NULL;
|
|
cursor_start = NULL;
|
|
cursor_impact = NULL;
|
|
cursor_entitynumber = NULL;
|
|
}
|
|
|
|
f = MSG_ReadShort() * (1.0f / 32767.0f);
|
|
if (cursor_screen) cursor_screen->_vector[0] = f;
|
|
f = MSG_ReadShort() * (1.0f / 32767.0f);
|
|
if (cursor_screen) cursor_screen->_vector[1] = f;
|
|
|
|
f = MSG_ReadFloat();
|
|
if (cursor_start) cursor_start->_vector[0] = f;
|
|
f = MSG_ReadFloat();
|
|
if (cursor_start) cursor_start->_vector[1] = f;
|
|
f = MSG_ReadFloat();
|
|
if (cursor_start) cursor_start->_vector[2] = f;
|
|
|
|
f = MSG_ReadFloat();
|
|
if (cursor_impact) cursor_impact->_vector[0] = f;
|
|
f = MSG_ReadFloat();
|
|
if (cursor_impact) cursor_impact->_vector[1] = f;
|
|
f = MSG_ReadFloat();
|
|
if (cursor_impact) cursor_impact->_vector[2] = f;
|
|
|
|
entnum = MSGSV_ReadEntity(host_client);
|
|
if (entnum >= sv.world.max_edicts)
|
|
{
|
|
Con_DPrintf("SV_ReadPrydonCursor: client send bad cursor_entitynumber\n");
|
|
entnum = 0;
|
|
}
|
|
// as requested by FrikaC, cursor_trace_ent is reset to world if the
|
|
// entity is free at time of receipt
|
|
if (!svprogfuncs || EDICT_NUM(svprogfuncs, entnum)->isfree)
|
|
entnum = 0;
|
|
if (msg_badread) Con_Printf("SV_ReadPrydonCursor: badread at %s:%i\n", __FILE__, __LINE__);
|
|
|
|
if (cursor_entitynumber) cursor_entitynumber->edict = entnum;
|
|
}
|
|
|
|
void SV_ReadQCRequest(void)
|
|
{
|
|
int e;
|
|
char args[8];
|
|
char *rname;
|
|
func_t f;
|
|
int i;
|
|
globalvars_t *pr_globals;
|
|
|
|
if (!svprogfuncs)
|
|
{
|
|
msg_badread = true;
|
|
return;
|
|
}
|
|
|
|
pr_globals = PR_globals(svprogfuncs, PR_CURRENT);
|
|
|
|
for (i = 0; ; i++)
|
|
{
|
|
if (i >= sizeof(args)-1)
|
|
{
|
|
if (MSG_ReadByte() != ev_void)
|
|
{
|
|
msg_badread = true;
|
|
return;
|
|
}
|
|
goto done;
|
|
}
|
|
switch(MSG_ReadByte())
|
|
{
|
|
case ev_void:
|
|
goto done;
|
|
case ev_float:
|
|
args[i] = 'f';
|
|
G_FLOAT(OFS_PARM0+i*3) = MSG_ReadFloat();
|
|
break;
|
|
case ev_vector:
|
|
args[i] = 'v';
|
|
G_FLOAT(OFS_PARM0+i*3+0) = MSG_ReadFloat();
|
|
G_FLOAT(OFS_PARM0+i*3+1) = MSG_ReadFloat();
|
|
G_FLOAT(OFS_PARM0+i*3+2) = MSG_ReadFloat();
|
|
break;
|
|
case ev_integer:
|
|
args[i] = 'i';
|
|
G_INT(OFS_PARM0+i*3) = MSG_ReadLong();
|
|
break;
|
|
case ev_string:
|
|
args[i] = 's';
|
|
G_INT(OFS_PARM0+i*3) = PR_TempString(svprogfuncs, MSG_ReadString());
|
|
break;
|
|
case ev_entity:
|
|
args[i] = 'e';
|
|
e = MSGSV_ReadEntity(host_client);
|
|
if (e < 0 || e >= sv.world.num_edicts)
|
|
e = 0;
|
|
G_INT(OFS_PARM0+i*3) = EDICT_TO_PROG(svprogfuncs, EDICT_NUM(svprogfuncs, e));
|
|
break;
|
|
}
|
|
}
|
|
|
|
done:
|
|
args[i] = 0;
|
|
rname = MSG_ReadString();
|
|
if (i)
|
|
rname = va("Cmd_%s_%s", rname, args);
|
|
else
|
|
rname = va("Cmd_%s", rname);
|
|
f = PR_FindFunction(svprogfuncs, rname, PR_ANY);
|
|
if (f)
|
|
{
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
|
|
PR_ExecuteProgram(svprogfuncs, f);
|
|
}
|
|
else
|
|
SV_ClientPrintf(host_client, PRINT_HIGH, "qcrequest \"%s\" not supported\n", rname);
|
|
}
|
|
|
|
void SV_AckEntityFrame(client_t *cl, int framenum)
|
|
{
|
|
#ifdef PEXT_CSQC
|
|
if (cl->lastsequence_acknowledged + UPDATE_BACKUP > framenum)
|
|
{
|
|
int i;
|
|
for (i = cl->lastsequence_acknowledged+1; i < framenum; i++)
|
|
SV_CSQC_DroppedPacket(cl, i);
|
|
}
|
|
else
|
|
{
|
|
/*too much loss, we don't know what was sent when, so reset the entire entity state*/
|
|
SV_CSQC_DropAll(cl);
|
|
}
|
|
#endif
|
|
cl->lastsequence_acknowledged = framenum;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
SV_ExecuteClientMessage
|
|
|
|
The current net_message is parsed for the given client
|
|
===================
|
|
*/
|
|
void SV_ExecuteClientMessage (client_t *cl)
|
|
{
|
|
client_t *split = cl;
|
|
int c;
|
|
char *s;
|
|
usercmd_t oldest, oldcmd, newcmd;
|
|
client_frame_t *frame;
|
|
vec3_t o;
|
|
int checksumIndex;
|
|
qbyte checksum, calculatedChecksum;
|
|
int seq_hash, i;
|
|
|
|
// calc ping time
|
|
if (cl->frameunion.frames)
|
|
{ //split screen doesn't always have frames.
|
|
frame = &cl->frameunion.frames[cl->netchan.incoming_acknowledged & UPDATE_MASK];
|
|
|
|
if (cl->lastsequence_acknowledged + UPDATE_BACKUP > cl->netchan.incoming_acknowledged)
|
|
{
|
|
/*note that if there is packetloss, we can change a single frame's ping_time multiple times
|
|
this means that the 'ping' is more latency than ping times*/
|
|
if (frame->ping_time == -1 || !sv_ping_ignorepl.ival)
|
|
frame->ping_time = realtime - frame->senttime; //no more phenomanally low pings please
|
|
|
|
if (cl->spectator)
|
|
cl->delay = 0;
|
|
else
|
|
{
|
|
float diff = frame->ping_time*1000 - sv_minping.value;
|
|
if (fabs(diff) > 1)
|
|
{
|
|
//FIXME: we should use actual arrival times instead, so we don't get so much noise and seesawing.
|
|
diff = bound(-25, diff, 25); //don't swing wildly
|
|
cl->delay -= 0.001*(diff/25); //scale towards the ideal value
|
|
cl->delay = bound(0, cl->delay, 1); //but make sure things don't go crazy
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sv_antilag.ival)
|
|
{
|
|
/*
|
|
extern cvar_t temp1;
|
|
if (temp1.ival)
|
|
frame = &cl->frameunion.frames[(cl->netchan.incoming_acknowledged+temp1.ival) & UPDATE_MASK];
|
|
*/
|
|
#ifdef warningmsg
|
|
#pragma warningmsg("FIXME: make antilag optionally support non-player ents too")
|
|
#endif
|
|
for (i = 0; i < sv.allocated_client_slots; i++)
|
|
{
|
|
cl->laggedents[i].present = frame->playerpresent[i];
|
|
if (cl->laggedents[i].present)
|
|
VectorCopy(frame->playerpositions[i], cl->laggedents[i].laggedpos);
|
|
}
|
|
cl->laggedents_count = sv.allocated_client_slots;
|
|
|
|
cl->laggedents_frac = sv_antilag_frac.value;
|
|
}
|
|
else
|
|
cl->laggedents_count = 0;
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("Server bug: No frames!\n");
|
|
cl->send_message = false;
|
|
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->frameunion.frames)
|
|
{ //split screen doesn't always have frames.
|
|
cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].senttime = realtime;
|
|
cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].ping_time = -1;
|
|
cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].move_msecs = -1;
|
|
cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].packetsizein = net_message.cursize;
|
|
cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].packetsizeout = 0;
|
|
}
|
|
|
|
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 ("SVQW_ReadClientMessage: badread\n");
|
|
SV_DropClient (cl);
|
|
return;
|
|
}
|
|
|
|
c = MSG_ReadByte ();
|
|
if (c == -1)
|
|
break;
|
|
|
|
// Con_Printf("(%s) %i: %i\n", cl->name, msg_readcount, c);
|
|
|
|
switch (c)
|
|
{
|
|
default:
|
|
Con_Printf ("SVQW_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 (split == cl)
|
|
{
|
|
//only the first player is checksummed. its pointless as a security measure now quake is open source.
|
|
checksumIndex = MSG_GetReadCount();
|
|
checksum = (qbyte)MSG_ReadByte ();
|
|
|
|
//only the first player has packetloss calculated.
|
|
split->lossage = MSG_ReadByte();
|
|
}
|
|
else
|
|
{
|
|
checksumIndex = checksum = 0;
|
|
if (split)
|
|
split->lossage = cl->lossage;
|
|
}
|
|
MSG_ReadDeltaUsercmd (&nullcmd, &oldest);
|
|
MSG_ReadDeltaUsercmd (&oldest, &oldcmd);
|
|
MSG_ReadDeltaUsercmd (&oldcmd, &newcmd);
|
|
if (!split)
|
|
break; // either someone is trying to cheat, or they sent input commands for splitscreen clients they no longer own.
|
|
|
|
split->localtime = sv.time;
|
|
|
|
if (split->frameunion.frames)
|
|
split->frameunion.frames[split->netchan.outgoing_sequence&UPDATE_MASK].move_msecs = newcmd.msec;
|
|
|
|
if (split->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",
|
|
split->name, split->netchan.incoming_sequence, checksum, calculatedChecksum);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (split->iscrippled)
|
|
{
|
|
split->lastcmd.forwardmove = 0; //hmmm.... does this work well enough?
|
|
oldest.forwardmove = 0;
|
|
oldcmd.forwardmove = 0;
|
|
newcmd.forwardmove = 0;
|
|
|
|
split->lastcmd.sidemove = 0;
|
|
oldest.sidemove = 0;
|
|
oldcmd.sidemove = 0;
|
|
newcmd.sidemove = 0;
|
|
|
|
split->lastcmd.upmove = 0;
|
|
oldest.upmove = 0;
|
|
oldcmd.upmove = 0;
|
|
newcmd.upmove = 0;
|
|
}
|
|
|
|
host_client = split;
|
|
sv_player = split->edict;
|
|
#ifdef HLSERVER
|
|
if (svs.gametype == GT_HALFLIFE)
|
|
{
|
|
SVHL_RunPlayerCommand(split, &oldest, &oldcmd, &newcmd);
|
|
}
|
|
else
|
|
#endif
|
|
if (!sv.paused)
|
|
{
|
|
if (sv_nqplayerphysics.ival)
|
|
{
|
|
//store the info for the physics code to pick up the next time it ticks.
|
|
//yeah, nq sucks.
|
|
split->isindependant = false;
|
|
if (!split->edict->v->fixangle)
|
|
{
|
|
split->edict->v->v_angle[0] = newcmd.angles[0]* (360.0/65536);
|
|
split->edict->v->v_angle[1] = newcmd.angles[1]* (360.0/65536);
|
|
split->edict->v->v_angle[2] = newcmd.angles[2]* (360.0/65536);
|
|
}
|
|
|
|
if (newcmd.impulse)// && SV_FilterImpulse(newcmd.impulse, host_client->trustlevel))
|
|
split->edict->v->impulse = newcmd.impulse;
|
|
|
|
split->edict->v->button0 = newcmd.buttons & 1;
|
|
split->edict->v->button2 = (newcmd.buttons >> 1) & 1;
|
|
if (pr_allowbutton1.ival) //many mods use button1 - it's just a wasted field to many mods. So only work it if the cvar allows.
|
|
split->edict->v->button1 = ((newcmd.buttons >> 2) & 1);
|
|
// DP_INPUTBUTTONS
|
|
split->edict->xv->button3 = ((newcmd.buttons >> 2) & 1);
|
|
split->edict->xv->button4 = ((newcmd.buttons >> 3) & 1);
|
|
split->edict->xv->button5 = ((newcmd.buttons >> 4) & 1);
|
|
split->edict->xv->button6 = ((newcmd.buttons >> 5) & 1);
|
|
split->edict->xv->button7 = ((newcmd.buttons >> 6) & 1);
|
|
split->edict->xv->button8 = ((newcmd.buttons >> 7) & 1);
|
|
}
|
|
else
|
|
{
|
|
//run player physics instantly.
|
|
split->isindependant = true;
|
|
SV_PreRunCmd();
|
|
|
|
if (net_drop < 20)
|
|
{
|
|
while (net_drop > 2)
|
|
{
|
|
SV_RunCmd (&split->lastcmd, false);
|
|
net_drop--;
|
|
}
|
|
if (net_drop > 1)
|
|
SV_RunCmd (&oldest, false);
|
|
if (net_drop > 0)
|
|
SV_RunCmd (&oldcmd, false);
|
|
}
|
|
SV_RunCmd (&newcmd, false);
|
|
|
|
if (!SV_PlayerPhysicsQC || host_client->spectator)
|
|
SV_PostRunCmd();
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (newcmd.impulse)// && SV_FilterImpulse(newcmd.impulse, host_client->trustlevel))
|
|
sv_player->v->impulse = newcmd.impulse;
|
|
}
|
|
|
|
split->lastcmd = newcmd;
|
|
split->lastcmd.buttons = 0; // avoid multiple fires on lag
|
|
}
|
|
split = split->controlled; //so the next splitscreen client gets the next packet.
|
|
host_client = cl;
|
|
sv_player = cl->edict;
|
|
break;
|
|
|
|
case clc_prydoncursor:
|
|
SV_ReadPrydonCursor();
|
|
break;
|
|
case clc_qcrequest:
|
|
SV_ReadQCRequest();
|
|
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
|
|
#ifdef SERVER_DEMO_PLAYBACK
|
|
if (sv.mvdplayback)
|
|
{
|
|
VectorCopy(o, host_client->specorigin);
|
|
}
|
|
else
|
|
#endif
|
|
if (host_client->spectator)
|
|
{
|
|
VectorCopy(o, sv_player->v->origin);
|
|
World_LinkEdict(&sv.world, (wedict_t*)sv_player, false);
|
|
}
|
|
break;
|
|
|
|
case clc_upload:
|
|
SV_NextUpload();
|
|
break;
|
|
#ifdef VOICECHAT
|
|
case clc_voicechat:
|
|
SV_VoiceReadPacket();
|
|
break;
|
|
#endif
|
|
case clcdp_ackframe:
|
|
cl->delta_sequence = MSG_ReadLong();
|
|
SV_AckEntityFrame(cl, cl->delta_sequence);
|
|
break;
|
|
}
|
|
}
|
|
|
|
SV_AckEntityFrame(cl, cl->netchan.incoming_acknowledged);
|
|
|
|
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;
|
|
|
|
if (!ge)
|
|
{
|
|
Con_Printf("Q2 client without Q2 server\n");
|
|
SV_DropClient(cl);
|
|
}
|
|
|
|
// calc ping time
|
|
frame = &cl->frameunion.q2frames[cl->netchan.incoming_acknowledged & Q2UPDATE_MASK];
|
|
if (frame->senttime != -1)
|
|
{
|
|
int ping_time = realtime*1000 - frame->senttime; //no more phenomanally low pings please
|
|
if (ping_time > sv_minping.value+1)
|
|
{
|
|
cl->delay -= 0.001;
|
|
if (cl->delay < 0)
|
|
cl->delay = 0;
|
|
}
|
|
if (ping_time < sv_minping.value)
|
|
{
|
|
cl->delay += 0.001;
|
|
if (cl->delay > 1)
|
|
cl->delay = 1;
|
|
}
|
|
frame->senttime = -1;
|
|
frame->ping_time = ping_time;
|
|
}
|
|
|
|
// 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->frameunion.q2frames[cl->netchan.outgoing_sequence & Q2UPDATE_MASK].senttime = realtime*1000;
|
|
cl->frameunion.q2frames[cl->netchan.outgoing_sequence & Q2UPDATE_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 ("SVQ2_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 (net_drop < 20)
|
|
{
|
|
while (net_drop > 2)
|
|
{
|
|
ge->ClientThink (host_client->q2edict, (q2usercmd_t*)&cl->lastcmd);
|
|
net_drop--;
|
|
}
|
|
if (net_drop > 1)
|
|
ge->ClientThink (host_client->q2edict, (q2usercmd_t*)&oldest);
|
|
if (net_drop > 0)
|
|
ge->ClientThink (host_client->q2edict, (q2usercmd_t*)&oldcmd);
|
|
}
|
|
ge->ClientThink (host_client->q2edict, (q2usercmd_t*)&newcmd);
|
|
}
|
|
|
|
cl->lastcmd = newcmd;
|
|
break;
|
|
|
|
case clcq2_userinfo:
|
|
strncpy (cl->userinfo, MSG_ReadString (), sizeof(cl->userinfo)-1);
|
|
ge->ClientUserinfoChanged (cl->q2edict, cl->userinfo); //tell the gamecode
|
|
SV_ExtractFromUserinfo(cl, true); //let the server routines know
|
|
break;
|
|
|
|
case clcq2_stringcmd:
|
|
s = MSG_ReadString ();
|
|
SV_ExecuteUserCommand (s, false);
|
|
|
|
host_client = cl;
|
|
sv_player = cl->edict;
|
|
|
|
if (cl->state == cs_zombie)
|
|
return; // disconnect command
|
|
break;
|
|
|
|
#ifdef VOICECHAT
|
|
case clcq2_voicechat:
|
|
SV_VoiceReadPacket();
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef NQPROT
|
|
void SVNQ_ReadClientMove (usercmd_t *move)
|
|
{
|
|
int i;
|
|
int bits;
|
|
client_frame_t *frame;
|
|
float timesincelast;
|
|
float cltime;
|
|
|
|
frame = &host_client->frameunion.frames[host_client->netchan.incoming_acknowledged & UPDATE_MASK];
|
|
|
|
if (host_client->protocol == SCP_DARKPLACES7 || (host_client->fteprotocolextensions2 & PEXT2_PREDINFO))
|
|
host_client->last_sequence = MSG_ReadLong ();
|
|
else
|
|
host_client->last_sequence = 0;
|
|
cltime = MSG_ReadFloat ();
|
|
if (cltime < move->fservertime)
|
|
cltime = move->fservertime;
|
|
if (cltime > sv.time)
|
|
cltime = sv.time;
|
|
if (cltime < sv.time - 2) //if you do lag more than this, you won't get your free time.
|
|
cltime = sv.time - 2;
|
|
timesincelast = cltime - move->fservertime;
|
|
|
|
move->fservertime = cltime;
|
|
move->servertime = move->fservertime;
|
|
|
|
frame->ping_time = sv.time - cltime;
|
|
|
|
|
|
if (frame->ping_time*1000 > sv_minping.value+1)
|
|
{
|
|
host_client->delay -= 0.001;
|
|
if (host_client->delay < 0)
|
|
host_client->delay = 0;
|
|
}
|
|
if (frame->ping_time*1000 < sv_minping.value)
|
|
{
|
|
host_client->delay += 0.001;
|
|
if (host_client->delay > 1)
|
|
host_client->delay = 1;
|
|
}
|
|
|
|
|
|
// read current angles
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
if ((host_client->protocol == SCP_FITZ666 || host_client->protocol == SCP_PROQUAKE))
|
|
host_client->edict->v->v_angle[i] = MSG_ReadAngle16 ();
|
|
else
|
|
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=bound(0, timesincelast*1000, 255);
|
|
frame->move_msecs = timesincelast*1000;
|
|
|
|
// read buttons
|
|
if (host_client->protocol == SCP_DARKPLACES6 || host_client->protocol == SCP_DARKPLACES7 || (host_client->fteprotocolextensions2 & PEXT2_PRYDONCURSOR))
|
|
bits = MSG_ReadLong();
|
|
else
|
|
bits = MSG_ReadByte ();
|
|
move->buttons = bits;
|
|
|
|
i = MSG_ReadByte ();
|
|
if (i)
|
|
move->impulse = i;
|
|
|
|
if (host_client->protocol == SCP_DARKPLACES6 || host_client->protocol == SCP_DARKPLACES7 || (host_client->fteprotocolextensions2 & PEXT2_PRYDONCURSOR))
|
|
{
|
|
SV_ReadPrydonCursor();
|
|
}
|
|
|
|
if (SV_RunFullQCMovement(host_client, move))
|
|
{
|
|
pr_global_struct->time = sv.world.physicstime;
|
|
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
|
|
#ifdef VM_Q1
|
|
if (svs.gametype == GT_Q1QVM)
|
|
Q1QVM_PostThink();
|
|
else
|
|
#endif
|
|
{
|
|
if (pr_global_struct->PlayerPostThink)
|
|
PR_ExecuteProgram (svprogfuncs, pr_global_struct->PlayerPostThink);
|
|
}
|
|
host_client->isindependant = true;
|
|
return;
|
|
}
|
|
|
|
|
|
// if (i && SV_FilterImpulse(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.ival) //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->xv->button3 = ((bits >> 2) & 1);
|
|
host_client->edict->xv->button4 = ((bits >> 3) & 1);
|
|
host_client->edict->xv->button5 = ((bits >> 4) & 1);
|
|
host_client->edict->xv->button6 = ((bits >> 5) & 1);
|
|
host_client->edict->xv->button7 = ((bits >> 6) & 1);
|
|
host_client->edict->xv->button8 = ((bits >> 7) & 1);
|
|
|
|
if (host_client->last_sequence && !sv_nqplayerphysics.ival)
|
|
{
|
|
host_frametime = timesincelast;
|
|
|
|
host_client->isindependant = true;
|
|
SV_PreRunCmd();
|
|
SV_RunCmd (move, true);
|
|
SV_PostRunCmd();
|
|
}
|
|
else
|
|
host_client->isindependant = false;
|
|
}
|
|
|
|
void SVNQ_ExecuteClientMessage (client_t *cl)
|
|
{
|
|
int c;
|
|
char *s;
|
|
client_frame_t *frame;
|
|
|
|
cl->netchan.outgoing_sequence++;
|
|
cl->netchan.incoming_acknowledged = cl->netchan.outgoing_sequence-1;
|
|
|
|
// calc ping time
|
|
frame = &cl->frameunion.frames[cl->netchan.incoming_acknowledged & UPDATE_MASK];
|
|
frame->ping_time = -1;
|
|
|
|
// 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->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].senttime = realtime;
|
|
cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].ping_time = -1;
|
|
cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].move_msecs = -1;
|
|
cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].packetsizein = net_message.cursize;
|
|
cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].packetsizeout = 0;
|
|
|
|
host_client = cl;
|
|
sv_player = host_client->edict;
|
|
|
|
// mark time so clients will know how much to predict
|
|
// other players
|
|
cl->localtime = sv.time;
|
|
while (1)
|
|
{
|
|
if (msg_badread)
|
|
{
|
|
Con_Printf ("SVNQ_ReadClientMessage: badread\n");
|
|
SV_DropClient (cl);
|
|
return;
|
|
}
|
|
|
|
c = MSG_ReadByte ();
|
|
if (c == -1)
|
|
break;
|
|
|
|
switch (c)
|
|
{
|
|
default:
|
|
Con_Printf ("SVNQ_ReadClientMessage: unknown command char %i\n", c);
|
|
SV_DropClient (cl);
|
|
return;
|
|
|
|
case clc_disconnect:
|
|
host_client = cl;
|
|
sv_player = cl->edict;
|
|
SV_Drop_f();
|
|
return;
|
|
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 ();
|
|
SV_ExecuteUserCommand (s, false);
|
|
|
|
host_client = cl;
|
|
sv_player = cl->edict;
|
|
if (cl->state < cs_connected)
|
|
return;
|
|
break;
|
|
|
|
case clc_qcrequest:
|
|
SV_ReadQCRequest();
|
|
break;
|
|
|
|
case clcdp_ackframe:
|
|
cl->delta_sequence = MSG_ReadLong();
|
|
SV_AckEntityFrame(cl, cl->delta_sequence);
|
|
break;
|
|
case clcdp_ackdownloaddata:
|
|
SV_DarkPlacesDownloadAck(cl);
|
|
break;
|
|
|
|
#ifdef VOICECHAT
|
|
case clc_voicechat:
|
|
SV_VoiceReadPacket();
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
/*
|
|
==============
|
|
SV_UserInit
|
|
==============
|
|
*/
|
|
void SV_UserInit (void)
|
|
{
|
|
#ifdef VOICECHAT
|
|
Cvar_Register (&sv_voip, cvargroup_serverpermissions);
|
|
Cvar_Register (&sv_voip_echo, cvargroup_serverpermissions);
|
|
Cvar_Register (&sv_voip_record, cvargroup_serverpermissions);
|
|
#endif
|
|
#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_fullredirect, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_antilag, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_antilag_frac, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_cheatpc, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_cheatspeedchecktime, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_playermodelchecks, cvargroup_servercontrol);
|
|
|
|
Cvar_Register (&sv_getrealip, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_realip_kick, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_realiphostname_ipv4, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_realiphostname_ipv6, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_realip_timeout, cvargroup_servercontrol);
|
|
|
|
Cvar_Register (&sv_pushplayers, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_protocol_nq, cvargroup_servercontrol);
|
|
|
|
Cvar_Register (&sv_pure, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_floodprotect, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_floodprotect_interval, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_floodprotect_messages, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_floodprotect_silencetime, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_floodprotect_suicide, cvargroup_servercontrol);
|
|
Cvar_Register (&sv_floodprotect_sendmessage, cvargroup_servercontrol);
|
|
|
|
Cvar_Register (&sv_cmdlikercon, cvargroup_serverpermissions);
|
|
Cvar_Register(&cmd_gamecodelevel, "Access controls");
|
|
Cvar_Register(&cmd_allowaccess, "Access controls");
|
|
|
|
Cvar_Register (&votelevel, sv_votinggroup);
|
|
Cvar_Register (&voteminimum, sv_votinggroup);
|
|
Cvar_Register (&votepercent, sv_votinggroup);
|
|
Cvar_Register (&votetime, sv_votinggroup);
|
|
|
|
Cvar_Register (&sv_brokenmovetypes, "Backwards compatability");
|
|
|
|
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
|
|
|
|
==================
|
|
*/
|
|
static 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 = World_Move (&sv.world, start, vec3_origin, vec3_origin, stop, true, (wedict_t*)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;
|
|
}
|
|
|
|
static 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];
|
|
}
|
|
|
|
static 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
|
|
|
|
===================
|
|
*/
|
|
static 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->xv->maxspeed;//FIXME: This isn't fully compatible code...
|
|
if (sv_player->xv->hasted)
|
|
maxspeed*=sv_player->xv->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);
|
|
}
|
|
}
|
|
|
|
static 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];
|
|
}
|
|
|
|
static 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;
|
|
|
|
if (host_client->state && host_client->protocol != SCP_BAD)
|
|
{
|
|
sv_player->xv->movement[0] = cmd.forwardmove;
|
|
sv_player->xv->movement[1] = cmd.sidemove;
|
|
sv_player->xv->movement[2] = cmd.upmove;
|
|
}
|
|
|
|
if (SV_PlayerPhysicsQC && !host_client->spectator)
|
|
{
|
|
pr_global_struct->time = sv.world.physicstime;
|
|
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
|