mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-11 07:42:18 +00:00
941 lines
23 KiB
C
941 lines
23 KiB
C
/*
|
|
#FILENAME#
|
|
|
|
#DESCRIPTION#
|
|
|
|
Copyright (C) 2004 #AUTHOR#
|
|
|
|
Author: #AUTHOR#
|
|
Date: #DATE#
|
|
|
|
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:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
static __attribute__ ((unused)) const char rcsid[] =
|
|
"$Id$";
|
|
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "QF/cbuf.h"
|
|
#include "QF/cmd.h"
|
|
#include "QF/console.h"
|
|
#include "QF/checksum.h"
|
|
#include "QF/dstring.h"
|
|
#include "QF/hash.h"
|
|
#include "QF/idparse.h"
|
|
#include "QF/info.h"
|
|
#include "QF/va.h"
|
|
|
|
#include "qw/bothdefs.h"
|
|
#include "qw/msg_ucmd.h"
|
|
#include "qw/protocol.h"
|
|
|
|
#include "client.h"
|
|
#include "connection.h"
|
|
#include "qtv.h"
|
|
#include "server.h"
|
|
|
|
static client_t *clients;
|
|
|
|
typedef struct ucmd_s {
|
|
const char *name;
|
|
void (*func) (client_t *cl, void *userdata);
|
|
unsigned no_redirect:1;
|
|
unsigned overridable:1;
|
|
unsigned freeable:1;
|
|
void *userdata;
|
|
void (*on_free) (void *userdata);
|
|
} ucmd_t;
|
|
|
|
static void
|
|
client_drop (client_t *cl)
|
|
{
|
|
MSG_WriteByte (&cl->netchan.message, svc_disconnect);
|
|
qtv_printf ("Client %s removed\n", Info_ValueForKey (cl->userinfo, "name"));
|
|
cl->drop = 1;
|
|
}
|
|
|
|
static void
|
|
cl_new_f (client_t *cl, void *unused)
|
|
{
|
|
qtv_printf ("\"cmd list\" for a list of servers\n");
|
|
qtv_printf ("\"cmd connect <servername>\" to connect to a server\n");
|
|
}
|
|
|
|
static void
|
|
cl_modellist_f (client_t *cl, void *unused)
|
|
{
|
|
server_t *sv = cl->server;
|
|
unsigned n;
|
|
char **s;
|
|
|
|
if (atoi (Cmd_Argv (1)) != sv->spawncount) {
|
|
qtv_printf ("modellist from different level\n");
|
|
Client_New (cl);
|
|
return;
|
|
}
|
|
n = atoi (Cmd_Argv (2));
|
|
if (n >= MAX_MODELS) {
|
|
qtv_printf ("invalid modellist index\n");
|
|
Client_New (cl);
|
|
return;
|
|
}
|
|
|
|
MSG_WriteByte (&cl->netchan.message, svc_modellist);
|
|
MSG_WriteByte (&cl->netchan.message, n);
|
|
for (s = sv->modellist + n;
|
|
*s && cl->netchan.message.cursize < (MAX_MSGLEN /2);
|
|
s++, n++)
|
|
MSG_WriteString (&cl->netchan.message, *s);
|
|
MSG_WriteByte (&cl->netchan.message, 0);
|
|
if (*s)
|
|
MSG_WriteByte (&cl->netchan.message, n);
|
|
else
|
|
MSG_WriteByte (&cl->netchan.message, 0);
|
|
}
|
|
|
|
static void
|
|
cl_soundlist_f (client_t *cl, void *unused)
|
|
{
|
|
server_t *sv = cl->server;
|
|
unsigned n;
|
|
char **s;
|
|
|
|
if (!cl->server)
|
|
return;
|
|
if (atoi (Cmd_Argv (1)) != sv->spawncount) {
|
|
qtv_printf ("soundlist from different level\n");
|
|
Client_New (cl);
|
|
return;
|
|
}
|
|
n = atoi (Cmd_Argv (2));
|
|
if (n >= MAX_SOUNDS) {
|
|
qtv_printf ("invalid soundlist index\n");
|
|
Client_New (cl);
|
|
return;
|
|
}
|
|
|
|
MSG_WriteByte (&cl->netchan.message, svc_soundlist);
|
|
MSG_WriteByte (&cl->netchan.message, n);
|
|
for (s = sv->soundlist + n;
|
|
*s && cl->netchan.message.cursize < (MAX_MSGLEN /2);
|
|
s++, n++)
|
|
MSG_WriteString (&cl->netchan.message, *s);
|
|
MSG_WriteByte (&cl->netchan.message, 0);
|
|
if (*s)
|
|
MSG_WriteByte (&cl->netchan.message, n);
|
|
else
|
|
MSG_WriteByte (&cl->netchan.message, 0);
|
|
}
|
|
|
|
static void
|
|
cl_prespawn_f (client_t *cl, void *unused)
|
|
{
|
|
const char *cmd;
|
|
server_t *sv = cl->server;
|
|
int buf, size;
|
|
sizebuf_t *msg;
|
|
|
|
if (!cl->server)
|
|
return;
|
|
if (atoi (Cmd_Argv (1)) != sv->spawncount) {
|
|
qtv_printf ("prespawn from different level\n");
|
|
Client_New (cl);
|
|
return;
|
|
}
|
|
buf = atoi (Cmd_Argv (2));
|
|
if (buf >= sv->num_signon_buffers)
|
|
buf = 0;
|
|
if (buf == sv->num_signon_buffers - 1)
|
|
cmd = va ("cmd spawn %i 0\n", cl->server->spawncount);
|
|
else
|
|
cmd = va ("cmd prespawn %i %i\n", cl->server->spawncount, buf + 1);
|
|
size = sv->signon_buffer_size[buf] + 1 + strlen (cmd) + 1;
|
|
msg = MSG_ReliableCheckBlock (&cl->backbuf, size);
|
|
SZ_Write (msg, sv->signon_buffers[buf], sv->signon_buffer_size[buf]);
|
|
MSG_WriteByte (msg, svc_stufftext);
|
|
MSG_WriteString (msg, cmd);
|
|
}
|
|
|
|
static void
|
|
cl_spawn_f (client_t *cl, void *unused)
|
|
{
|
|
const char *cmd;
|
|
server_t *sv = cl->server;
|
|
const char *info;
|
|
player_t *pl;
|
|
int i;
|
|
sizebuf_t *msg;
|
|
|
|
if (!cl->server)
|
|
return;
|
|
if (atoi (Cmd_Argv (1)) != sv->spawncount) {
|
|
qtv_printf ("spawn from different level\n");
|
|
Client_New (cl);
|
|
return;
|
|
}
|
|
for (i = 0, pl = sv->players; i < MAX_SV_PLAYERS; i++, pl++) {
|
|
if (!pl->info)
|
|
continue;
|
|
msg = MSG_ReliableCheckBlock (&cl->backbuf, 24 + pl->info->cursize);
|
|
MSG_WriteByte (msg, svc_updatefrags);
|
|
MSG_WriteByte (msg, i);
|
|
MSG_WriteShort (msg, pl->frags);
|
|
MSG_WriteByte (msg, svc_updateping);
|
|
MSG_WriteByte (msg, i);
|
|
MSG_WriteShort (msg, 333/*XXX*/);
|
|
MSG_WriteByte (msg, svc_updatepl);
|
|
MSG_WriteByte (msg, i);
|
|
MSG_WriteByte (msg, 0/*XXX*/);
|
|
MSG_WriteByte (msg, svc_updateentertime);
|
|
MSG_WriteByte (msg, i);
|
|
MSG_WriteFloat (msg, 0/*XXX*/);
|
|
info = pl->info ? Info_MakeString (pl->info, 0) : "";
|
|
MSG_WriteByte (msg, svc_updateuserinfo);
|
|
MSG_WriteByte (msg, i);
|
|
MSG_WriteLong (msg, pl->uid);
|
|
MSG_WriteString (msg, info);
|
|
if (cl->backbuf.num_backbuf)
|
|
MSG_Reliable_FinishWrite (&cl->backbuf);
|
|
}
|
|
for (i = 0; i < MAX_LIGHTSTYLES; i++) {
|
|
MSG_ReliableWrite_Begin (&cl->backbuf, svc_lightstyle,
|
|
3 + (sv->lightstyles[i] ?
|
|
strlen (sv->lightstyles[i]) : 1));
|
|
MSG_ReliableWrite_Byte (&cl->backbuf, i);
|
|
MSG_ReliableWrite_String (&cl->backbuf, sv->lightstyles[i]);
|
|
}
|
|
MSG_ReliableWrite_Begin (&cl->backbuf, svc_updatestatlong, 6);
|
|
MSG_ReliableWrite_Byte (&cl->backbuf, STAT_TOTALSECRETS);
|
|
MSG_ReliableWrite_Long (&cl->backbuf,
|
|
sv->players[0].stats[STAT_TOTALSECRETS]);
|
|
MSG_ReliableWrite_Begin (&cl->backbuf, svc_updatestatlong, 6);
|
|
MSG_ReliableWrite_Byte (&cl->backbuf, STAT_TOTALMONSTERS);
|
|
MSG_ReliableWrite_Long (&cl->backbuf,
|
|
sv->players[0].stats[STAT_TOTALMONSTERS]);
|
|
MSG_ReliableWrite_Begin (&cl->backbuf, svc_updatestatlong, 6);
|
|
MSG_ReliableWrite_Byte (&cl->backbuf, STAT_SECRETS);
|
|
MSG_ReliableWrite_Long (&cl->backbuf, sv->players[0].stats[STAT_SECRETS]);
|
|
MSG_ReliableWrite_Begin (&cl->backbuf, svc_updatestatlong, 6);
|
|
MSG_ReliableWrite_Byte (&cl->backbuf, STAT_SECRETS);
|
|
MSG_ReliableWrite_Long (&cl->backbuf, sv->players[0].stats[STAT_SECRETS]);
|
|
cmd = "skins\n";
|
|
MSG_ReliableWrite_Begin (&cl->backbuf, svc_stufftext, strlen (cmd) + 2);
|
|
MSG_ReliableWrite_String (&cl->backbuf, cmd);
|
|
}
|
|
|
|
static void
|
|
cl_begin_f (client_t *cl, void *unused)
|
|
{
|
|
server_t *sv = cl->server;
|
|
|
|
if (!cl->server)
|
|
return;
|
|
if (atoi (Cmd_Argv (1)) != sv->spawncount) {
|
|
qtv_printf ("spawn from different level\n");
|
|
Client_New (cl);
|
|
return;
|
|
}
|
|
cl->connected = 1;
|
|
}
|
|
|
|
static void
|
|
cl_drop_f (client_t *cl, void *unused)
|
|
{
|
|
if (cl->server)
|
|
Server_Disconnect (cl);
|
|
client_drop (cl);
|
|
}
|
|
|
|
static void
|
|
cl_pings_f (client_t *cl, void *unused)
|
|
{
|
|
}
|
|
|
|
static void
|
|
cl_rate_f (client_t *cl, void *unused)
|
|
{
|
|
}
|
|
|
|
static void
|
|
cl_say_f (client_t *cl, void *unused)
|
|
{
|
|
}
|
|
|
|
static void
|
|
cl_setinfo_f (client_t *cl, void *unused)
|
|
{
|
|
}
|
|
|
|
static void
|
|
cl_serverinfo_f (client_t *cl, void *unused)
|
|
{
|
|
if (cl->server) {
|
|
Info_Print (cl->server->info);
|
|
return;
|
|
}
|
|
qtv_printf ("not connected to a server");
|
|
}
|
|
|
|
static void
|
|
cl_download_f (client_t *cl, void *unused)
|
|
{
|
|
qtv_printf ("download denied\n");
|
|
MSG_ReliableWrite_Begin (&cl->backbuf, svc_download, 4);
|
|
MSG_ReliableWrite_Short (&cl->backbuf, -1);
|
|
MSG_ReliableWrite_Byte (&cl->backbuf, 0);
|
|
}
|
|
|
|
static void
|
|
cl_nextdl_f (client_t *cl, void *unused)
|
|
{
|
|
}
|
|
|
|
static void
|
|
cl_ptrack_f (client_t *cl, void *unused)
|
|
{
|
|
}
|
|
|
|
static void
|
|
cl_list_f (client_t *cl, void *unused)
|
|
{
|
|
Server_List ();
|
|
}
|
|
|
|
static void
|
|
cl_connect_f (client_t *cl, void *unused)
|
|
{
|
|
if (cl->server) {
|
|
qtv_printf ("already connected to server %s\n", cl->server->name);
|
|
qtv_printf ("\"cmd disconnect\" first\n");
|
|
return;
|
|
}
|
|
Server_Connect (Cmd_Argv (1), cl);
|
|
}
|
|
|
|
static void
|
|
cl_disconnect_f (client_t *cl, void *unused)
|
|
{
|
|
if (!cl->server) {
|
|
qtv_printf ("not connected to a server\n");
|
|
return;
|
|
}
|
|
client_drop (cl);
|
|
}
|
|
|
|
static ucmd_t ucmds[] = {
|
|
{"new", cl_new_f, 0, 0},
|
|
{"modellist", cl_modellist_f, 0, 0},
|
|
{"soundlist", cl_soundlist_f, 0, 0},
|
|
{"prespawn", cl_prespawn_f, 0, 0},
|
|
{"spawn", cl_spawn_f, 0, 0},
|
|
{"begin", cl_begin_f, 1, 0},
|
|
|
|
{"drop", cl_drop_f, 1, 0},
|
|
{"pings", cl_pings_f, 0, 0},
|
|
|
|
// issued by hand at client consoles
|
|
{"rate", cl_rate_f, 0, 0},
|
|
{"kill", 0, 1, 1},
|
|
{"pause", 0, 1, 0},
|
|
{"msg", 0, 0, 0},
|
|
|
|
{"say", cl_say_f, 1, 1},
|
|
{"say_team", cl_say_f, 1, 1},
|
|
|
|
{"setinfo", cl_setinfo_f, 1, 0},
|
|
|
|
{"serverinfo", cl_serverinfo_f, 0, 0},
|
|
|
|
{"download", cl_download_f, 1, 0},
|
|
{"nextdl", cl_nextdl_f, 0, 0},
|
|
|
|
{"ptrack", cl_ptrack_f, 0, 1}, // ZOID - used with autocam
|
|
|
|
{"snap", 0, 0, 0},
|
|
|
|
{"list", cl_list_f, 0, 0},
|
|
{"connect", cl_connect_f, 0, 0},
|
|
{"disconnect", cl_disconnect_f, 0, 0},
|
|
};
|
|
|
|
static hashtab_t *ucmd_table;
|
|
int (*ucmd_unknown)(void);
|
|
|
|
static void
|
|
client_exec_command (client_t *cl, const char *s)
|
|
{
|
|
ucmd_t *u;
|
|
|
|
COM_TokenizeString (s, qtv_args);
|
|
cmd_args = qtv_args;
|
|
|
|
u = (ucmd_t*) Hash_Find (ucmd_table, qtv_args->argv[0]->str);
|
|
|
|
if (!u) {
|
|
if (!ucmd_unknown || !ucmd_unknown ()) {
|
|
qtv_begin_redirect (RD_CLIENT, cl);
|
|
qtv_printf ("Bad user command: %s\n", qtv_args->argv[0]->str);
|
|
qtv_end_redirect ();
|
|
}
|
|
} else {
|
|
if (u->func) {
|
|
if (!u->no_redirect)
|
|
qtv_begin_redirect (RD_CLIENT, cl);
|
|
u->func (cl, u->userdata);
|
|
if (!u->no_redirect)
|
|
qtv_end_redirect ();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
spectator_move (client_t *cl, usercmd_t *ucmd)
|
|
{
|
|
float frametime = ucmd->msec * 0.001;
|
|
float control, drop, friction, fmove, smove, speed, newspeed;
|
|
float currentspeed, addspeed, accelspeed, wishspeed;
|
|
int i;
|
|
vec3_t wishdir, wishvel;
|
|
vec3_t forward, right, up;
|
|
server_t *sv = cl->server;
|
|
|
|
AngleVectors (cl->state.cmd.angles, forward, right, up);
|
|
|
|
speed = DotProduct (cl->state.velocity, cl->state.velocity);
|
|
if (speed < 1) {
|
|
VectorZero (cl->state.velocity);
|
|
} else {
|
|
speed = sqrt (speed);
|
|
drop = 0;
|
|
|
|
friction = sv->movevars.friction * 1.5;
|
|
control = speed < sv->movevars.stopspeed ? sv->movevars.stopspeed
|
|
: speed;
|
|
drop += control * friction * frametime;
|
|
newspeed = speed - drop;
|
|
if (newspeed < 0)
|
|
newspeed = 0;
|
|
newspeed /= speed;
|
|
|
|
VectorScale (cl->state.velocity, newspeed, cl->state.velocity);
|
|
}
|
|
|
|
fmove = ucmd->forwardmove;
|
|
smove = ucmd->sidemove;
|
|
|
|
VectorNormalize (forward);
|
|
VectorNormalize (right);
|
|
|
|
for (i = 0; i < 3; i++)
|
|
wishvel[i] = forward[i] * fmove + right[i] * smove;
|
|
wishvel[2] += ucmd->upmove;
|
|
|
|
VectorCopy (wishvel, wishdir);
|
|
wishspeed = VectorNormalize (wishdir);
|
|
|
|
if (wishspeed > sv->movevars.spectatormaxspeed) {
|
|
VectorScale (wishvel, sv->movevars.spectatormaxspeed / wishspeed,
|
|
wishvel);
|
|
wishspeed = sv->movevars.spectatormaxspeed;
|
|
}
|
|
|
|
currentspeed = DotProduct (cl->state.velocity, wishdir);
|
|
addspeed = wishspeed - currentspeed;
|
|
if (addspeed <= 0)
|
|
return;
|
|
accelspeed = sv->movevars.accelerate * frametime * wishspeed;
|
|
if (accelspeed > addspeed)
|
|
accelspeed = addspeed;
|
|
|
|
VectorMultAdd (cl->state.velocity, accelspeed, wishdir,
|
|
cl->state.velocity);
|
|
VectorMultAdd (cl->state.origin, frametime, cl->state.velocity,
|
|
cl->state.origin);
|
|
}
|
|
|
|
static void
|
|
run_command (client_t *cl, usercmd_t *ucmd)
|
|
{
|
|
if (ucmd->msec > 50) {
|
|
usercmd_t cmd = *ucmd;
|
|
int oldmsec = ucmd->msec;
|
|
cmd.msec = oldmsec / 2;
|
|
run_command (cl, &cmd);
|
|
cmd.msec = oldmsec / 2;
|
|
cmd.impulse = 0;
|
|
run_command (cl, &cmd);
|
|
return;
|
|
}
|
|
|
|
VectorCopy (ucmd->angles, cl->state.cmd.angles);
|
|
|
|
spectator_move (cl, ucmd);
|
|
}
|
|
|
|
static void
|
|
client_parse_message (client_t *cl)
|
|
{
|
|
int c, size;
|
|
vec3_t o;
|
|
const char *s;
|
|
usercmd_t oldest, oldcmd, newcmd;
|
|
byte checksum, calculatedChecksum;
|
|
int checksumIndex, seq_hash;
|
|
qboolean move_issued = false;
|
|
|
|
seq_hash = cl->netchan.incoming_sequence;
|
|
|
|
while (1) {
|
|
if (net_message->badread) {
|
|
qtv_printf ("SV_ReadClientMessage: badread\n");
|
|
client_drop (cl);
|
|
return;
|
|
}
|
|
|
|
c = MSG_ReadByte (net_message);
|
|
if (c == -1)
|
|
return;
|
|
|
|
switch (c) {
|
|
default:
|
|
qtv_printf ("SV_ReadClientMessage: unknown command char\n");
|
|
client_drop (cl);
|
|
return;
|
|
case clc_nop:
|
|
break;
|
|
case clc_delta:
|
|
/*cl->delta_sequence = */MSG_ReadByte (net_message);
|
|
break;
|
|
case clc_move:
|
|
checksumIndex = MSG_GetReadCount (net_message);
|
|
checksum = MSG_ReadByte (net_message);
|
|
// read loss percentage
|
|
/*cl->lossage = */MSG_ReadByte (net_message);
|
|
MSG_ReadDeltaUsercmd (net_message, &nullcmd, &oldest);
|
|
MSG_ReadDeltaUsercmd (net_message, &oldest, &oldcmd);
|
|
MSG_ReadDeltaUsercmd (net_message, &oldcmd, &newcmd);
|
|
if (!cl->server)
|
|
break;
|
|
if (move_issued)
|
|
break; // someone is trying to cheat...
|
|
move_issued = true;
|
|
// if (cl->state != cs_spawned)
|
|
// break;
|
|
// if the checksum fails, ignore the rest of the packet
|
|
calculatedChecksum =
|
|
COM_BlockSequenceCRCByte (net_message->message->data +
|
|
checksumIndex + 1,
|
|
MSG_GetReadCount (net_message) -
|
|
checksumIndex - 1, seq_hash);
|
|
if (calculatedChecksum != checksum) {
|
|
Con_DPrintf
|
|
("Failed command checksum for %s(%d) (%d != %d)\n",
|
|
Info_ValueForKey (cl->userinfo, "name"),
|
|
cl->netchan.incoming_sequence, checksum,
|
|
calculatedChecksum);
|
|
return;
|
|
}
|
|
// if (!sv.paused) {
|
|
// SV_PreRunCmd ();
|
|
if (cl->netchan.net_drop < 20) {
|
|
while (cl->netchan.net_drop > 2) {
|
|
run_command (cl, &cl->lastcmd);
|
|
cl->netchan.net_drop--;
|
|
}
|
|
if (cl->netchan.net_drop > 1)
|
|
run_command (cl, &oldest);
|
|
if (cl->netchan.net_drop > 0)
|
|
run_command (cl, &oldcmd);
|
|
}
|
|
run_command (cl, &newcmd);
|
|
// SV_PostRunCmd ();
|
|
// }
|
|
cl->lastcmd = newcmd;
|
|
cl->lastcmd.buttons = 0; // avoid multiple fires on lag
|
|
break;
|
|
case clc_stringcmd:
|
|
s = MSG_ReadString (net_message);
|
|
client_exec_command (cl, s);
|
|
break;
|
|
case clc_tmove:
|
|
MSG_ReadCoordV (net_message, o);
|
|
VectorCopy (o, cl->state.origin);
|
|
break;
|
|
case clc_upload:
|
|
size = MSG_ReadShort (net_message);
|
|
MSG_ReadByte (net_message);
|
|
net_message->readcount += size;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
write_player (int num, plent_state_t *pl, server_t *sv, sizebuf_t *msg)
|
|
{
|
|
int i;
|
|
int pflags = (pl->flags & (PF_GIB | PF_DEAD))
|
|
| PF_MSEC | PF_COMMAND;
|
|
int qf_bits = 0;
|
|
|
|
if (pl->modelindex != sv->playermodel)
|
|
pflags |= PF_MODEL;
|
|
for (i = 0; i < 3; i++)
|
|
if (pl->velocity[i])
|
|
pflags |= PF_VELOCITY1 << i;
|
|
if (pl->effects & 0xff)
|
|
pflags |= PF_EFFECTS;
|
|
if (pl->skinnum)
|
|
pflags |= PF_SKINNUM;
|
|
|
|
qf_bits = 0;
|
|
#if 0
|
|
if (client->stdver > 1) {
|
|
if (sv_fields.alpha != -1 && pl->alpha)
|
|
qf_bits |= PF_ALPHA;
|
|
if (sv_fields.scale != -1 && pl->scale)
|
|
qf_bits |= PF_SCALE;
|
|
if (pl->effects & 0xff00)
|
|
qf_bits |= PF_EFFECTS2;
|
|
if (sv_fields.glow_size != -1 && pl->glow_size)
|
|
qf_bits |= PF_GLOWSIZE;
|
|
if (sv_fields.glow_color != -1 && pl->glow_color)
|
|
qf_bits |= PF_GLOWCOLOR;
|
|
if (sv_fields.colormod != -1
|
|
&& !VectorIsZero (pl->colormod))
|
|
qf_bits |= PF_COLORMOD;
|
|
if (pl->frame & 0xff00)
|
|
qf_bits |= PF_FRAME2;
|
|
if (qf_bits)
|
|
pflags |= PF_QF;
|
|
}
|
|
#endif
|
|
|
|
// if (cl->spectator) {
|
|
// // only sent origin and velocity to spectators
|
|
// pflags &= PF_VELOCITY1 | PF_VELOCITY2 | PF_VELOCITY3;
|
|
// } else if (ent == clent) {
|
|
// // don't send a lot of data on personal entity
|
|
// pflags &= ~(PF_MSEC | PF_COMMAND);
|
|
// if (pl->weaponframe)
|
|
// pflags |= PF_WEAPONFRAME;
|
|
// }
|
|
|
|
// if (client->spec_track && client->spec_track - 1 == j
|
|
// && pl->weaponframe)
|
|
// pflags |= PF_WEAPONFRAME;
|
|
|
|
MSG_WriteByte (msg, svc_playerinfo);
|
|
MSG_WriteByte (msg, num);
|
|
MSG_WriteShort (msg, pflags);
|
|
|
|
MSG_WriteCoordV (msg, pl->origin);
|
|
|
|
MSG_WriteByte (msg, pl->frame);
|
|
|
|
if (pflags & PF_MSEC) {
|
|
//msec = 1000 * (sv.time - cl->localtime);
|
|
//if (msec > 255)
|
|
// msec = 255;
|
|
MSG_WriteByte (msg, pl->msec);
|
|
}
|
|
|
|
if (pflags & PF_COMMAND) {
|
|
MSG_WriteDeltaUsercmd (msg, &nullcmd, &pl->cmd);
|
|
}
|
|
|
|
for (i = 0; i < 3; i++)
|
|
if (pflags & (PF_VELOCITY1 << i))
|
|
MSG_WriteShort (msg, pl->velocity[i]);
|
|
|
|
if (pflags & PF_MODEL)
|
|
MSG_WriteByte (msg, pl->modelindex);
|
|
|
|
if (pflags & PF_SKINNUM)
|
|
MSG_WriteByte (msg, pl->skinnum);
|
|
|
|
if (pflags & PF_EFFECTS)
|
|
MSG_WriteByte (msg, pl->effects);
|
|
|
|
if (pflags & PF_WEAPONFRAME)
|
|
MSG_WriteByte (msg, pl->weaponframe);
|
|
|
|
if (pflags & PF_QF) {
|
|
MSG_WriteByte (msg, qf_bits);
|
|
if (qf_bits & PF_ALPHA)
|
|
MSG_WriteByte (msg, pl->alpha);
|
|
if (qf_bits & PF_SCALE)
|
|
MSG_WriteByte (msg, pl->scale);
|
|
if (qf_bits & PF_EFFECTS2)
|
|
MSG_WriteByte (msg, pl->effects >> 8);
|
|
if (qf_bits & PF_GLOWSIZE)
|
|
MSG_WriteByte (msg, pl->scale);
|
|
if (qf_bits & PF_GLOWCOLOR)
|
|
MSG_WriteByte (msg, pl->glow_color);
|
|
if (qf_bits & PF_COLORMOD)
|
|
MSG_WriteByte (msg, pl->colormod);
|
|
if (qf_bits & PF_FRAME2)
|
|
MSG_WriteByte (msg, pl->frame >> 8);
|
|
}
|
|
}
|
|
|
|
static void
|
|
write_entities (client_t *client, sizebuf_t *msg)
|
|
{
|
|
server_t *sv = client->server;
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_SV_PLAYERS; i++) {
|
|
if (!sv->players[i].info)
|
|
continue;
|
|
write_player (i, &sv->players[i].ent, sv, msg);
|
|
}
|
|
}
|
|
|
|
static void
|
|
cl_send_messages (client_t *cl)
|
|
{
|
|
byte buf[MAX_DATAGRAM];
|
|
sizebuf_t msg;
|
|
|
|
memset (&msg, 0, sizeof (msg));
|
|
msg.allowoverflow = true;
|
|
msg.maxsize = sizeof (buf);
|
|
msg.data = buf;
|
|
|
|
if (cl->backbuf.num_backbuf)
|
|
MSG_Reliable_Send (&cl->backbuf);
|
|
if (cl->connected) {
|
|
write_entities (cl, &msg);
|
|
write_player (31, &cl->state, cl->server, &msg);
|
|
MSG_WriteByte (&msg, svc_packetentities);
|
|
MSG_WriteShort (&msg, 0);
|
|
if (cl->datagram.cursize) {
|
|
SZ_Write (&msg, cl->datagram.data, cl->datagram.cursize);
|
|
SZ_Clear (&cl->datagram);
|
|
}
|
|
}
|
|
Netchan_Transmit (&cl->netchan, msg.cursize, msg.data);
|
|
}
|
|
|
|
static void
|
|
client_handler (connection_t *con, void *object)
|
|
{
|
|
client_t *cl = (client_t *) object;
|
|
|
|
if (net_message->message->cursize < 11) {
|
|
qtv_printf ("%s: Runt packet\n", NET_AdrToString (net_from));
|
|
return;
|
|
}
|
|
if (Netchan_Process (&cl->netchan)) {
|
|
// this is a valid, sequenced packet, so process it
|
|
//svs.stats.packets++;
|
|
cl->send_message = true;
|
|
//if (cl->state != cs_zombie)
|
|
client_parse_message (cl);
|
|
}
|
|
}
|
|
|
|
static void
|
|
client_connect (connection_t *con, void *object)
|
|
{
|
|
challenge_t *ch = (challenge_t *) object;
|
|
client_t *cl;
|
|
const char *str;
|
|
info_t *userinfo;
|
|
int version, qport, challenge, seq;
|
|
|
|
MSG_BeginReading (net_message);
|
|
seq = MSG_ReadLong (net_message);
|
|
if (seq != -1) {
|
|
qtv_printf ("unexpected connected packet\n");
|
|
return;
|
|
}
|
|
str = MSG_ReadString (net_message);
|
|
COM_TokenizeString (str, qtv_args);
|
|
cmd_args = qtv_args;
|
|
if (strcmp (Cmd_Argv (0), "connect")) {
|
|
qtv_printf ("unexpected connected packet\n");
|
|
return;
|
|
}
|
|
version = atoi (Cmd_Argv (1));
|
|
if (version != PROTOCOL_VERSION) {
|
|
Netchan_OutOfBandPrint (net_from, "%c\nServer is version %s.\n",
|
|
A2C_PRINT, QW_VERSION);
|
|
qtv_printf ("* rejected connect from version %i\n", version);
|
|
return;
|
|
}
|
|
qport = atoi (Cmd_Argv (2));
|
|
challenge = atoi (Cmd_Argv (3));
|
|
if (!(con = Connection_Find (&net_from))
|
|
|| (ch = con->object)->challenge != challenge) {
|
|
Netchan_OutOfBandPrint (net_from, "%c\nBad challenge.\n",
|
|
A2C_PRINT);
|
|
return;
|
|
}
|
|
free (con->object);
|
|
userinfo = Info_ParseString (Cmd_Argv (4), 0, 0);
|
|
if (!userinfo) {
|
|
Netchan_OutOfBandPrint (net_from, "%c\nInvalid userinfo string.\n",
|
|
A2C_PRINT);
|
|
return;
|
|
}
|
|
|
|
cl = calloc (1, sizeof (client_t));
|
|
Netchan_Setup (&cl->netchan, con->address, qport, NC_READ_QPORT);
|
|
cl->clnext = clients;
|
|
clients = cl;
|
|
cl->userinfo = userinfo;
|
|
cl->name = Info_ValueForKey (userinfo, "name");
|
|
cl->backbuf.name = cl->name;
|
|
cl->backbuf.netchan = &cl->netchan;
|
|
cl->con = con;
|
|
con->object = cl;
|
|
con->handler = client_handler;
|
|
|
|
cl->datagram.allowoverflow = true;
|
|
cl->datagram.maxsize = sizeof (cl->datagram_buf);
|
|
cl->datagram.data = cl->datagram_buf;
|
|
|
|
qtv_printf ("client %s (%s) connected\n",
|
|
Info_ValueForKey (userinfo, "name"),
|
|
NET_AdrToString (con->address));
|
|
|
|
Netchan_OutOfBandPrint (net_from, "%c", S2C_CONNECTION);
|
|
}
|
|
|
|
void
|
|
Client_NewConnection (void)
|
|
{
|
|
challenge_t *ch;
|
|
connection_t *con;
|
|
|
|
if ((con = Connection_Find (&net_from))) {
|
|
if (con->handler == client_handler)
|
|
return;
|
|
ch = con->object;
|
|
} else {
|
|
ch = malloc (sizeof (challenge_t));
|
|
}
|
|
ch->challenge = (rand () << 16) ^ rand ();
|
|
ch->time = realtime;
|
|
if (!con)
|
|
con = Connection_Add (&net_from, ch, 0);
|
|
Netchan_OutOfBandPrint (net_from, "%c%i QF", S2C_CHALLENGE, ch->challenge);
|
|
con->handler = client_connect;
|
|
}
|
|
|
|
static const char *
|
|
ucmds_getkey (void *_a, void *unused)
|
|
{
|
|
ucmd_t *a = (ucmd_t*)_a;
|
|
return a->name;
|
|
}
|
|
|
|
void
|
|
Client_Init (void)
|
|
{
|
|
size_t i;
|
|
|
|
ucmd_table = Hash_NewTable (251, ucmds_getkey, 0, 0);
|
|
for (i = 0; i < sizeof (ucmds) / sizeof (ucmds[0]); i++)
|
|
Hash_Add (ucmd_table, &ucmds[i]);
|
|
}
|
|
|
|
void
|
|
Client_New (client_t *cl)
|
|
{
|
|
server_t *sv = cl->server;
|
|
|
|
MSG_WriteByte (&cl->netchan.message, svc_serverdata);
|
|
MSG_WriteLong (&cl->netchan.message, PROTOCOL_VERSION);
|
|
MSG_WriteLong (&cl->netchan.message, sv->spawncount);
|
|
MSG_WriteString (&cl->netchan.message, sv->gamedir);
|
|
MSG_WriteByte (&cl->netchan.message, 0x80 | 31);
|
|
MSG_WriteString (&cl->netchan.message, sv->message);
|
|
MSG_WriteFloat (&cl->netchan.message, sv->movevars.gravity);
|
|
MSG_WriteFloat (&cl->netchan.message, sv->movevars.stopspeed);
|
|
MSG_WriteFloat (&cl->netchan.message, sv->movevars.maxspeed);
|
|
MSG_WriteFloat (&cl->netchan.message, sv->movevars.spectatormaxspeed);
|
|
MSG_WriteFloat (&cl->netchan.message, sv->movevars.accelerate);
|
|
MSG_WriteFloat (&cl->netchan.message, sv->movevars.airaccelerate);
|
|
MSG_WriteFloat (&cl->netchan.message, sv->movevars.wateraccelerate);
|
|
MSG_WriteFloat (&cl->netchan.message, sv->movevars.friction);
|
|
MSG_WriteFloat (&cl->netchan.message, sv->movevars.waterfriction);
|
|
MSG_WriteFloat (&cl->netchan.message, sv->movevars.entgravity);
|
|
MSG_WriteByte (&cl->netchan.message, svc_cdtrack);
|
|
MSG_WriteByte (&cl->netchan.message, sv->cdtrack);
|
|
MSG_WriteByte (&cl->netchan.message, svc_stufftext);
|
|
MSG_WriteString (&cl->netchan.message,
|
|
va ("fullserverinfo \"%s\"\n",
|
|
Info_MakeString (sv->info, 0)));
|
|
}
|
|
|
|
static void
|
|
delete_client (client_t *cl)
|
|
{
|
|
if (cl->server)
|
|
Server_Disconnect (cl);
|
|
Connection_Del (cl->con);
|
|
Info_Destroy (cl->userinfo);
|
|
free (cl);
|
|
}
|
|
|
|
void
|
|
Client_Frame (void)
|
|
{
|
|
client_t **c;
|
|
|
|
for (c = &clients; *c; ) {
|
|
client_t *cl = *c;
|
|
|
|
if (realtime - cl->netchan.last_received > 60) {
|
|
qtv_printf ("client %s timed out\n", (*c)->name);
|
|
client_drop (cl);
|
|
}
|
|
if (cl->netchan.message.overflowed) {
|
|
qtv_printf ("client %s overflowed\n", cl->name);
|
|
client_drop (cl);
|
|
}
|
|
if (cl->drop) {
|
|
qtv_printf ("client %s dropped\n", cl->name);
|
|
*c = cl->clnext;
|
|
delete_client (cl);
|
|
continue;
|
|
}
|
|
if (cl->send_message) {
|
|
cl_send_messages (cl);
|
|
cl->send_message = false;
|
|
}
|
|
c = &(*c)->clnext;
|
|
}
|
|
}
|