quakeforge/qtv/source/client.c
Bill Currie 5f93c115ff [util] Make developer flag names easier to manage
They're now an enum, and the flag part of the name is all lowercase, but
now the flag definitions and names list will never get out of sync.
2021-03-29 22:38:47 +09:00

1281 lines
31 KiB
C

/*
client.c
quakeworld client processing
Copyright (C) 2004 Bill Currie <bill@taniwha.org>
Author: Bill Currie
Date: 2004/02/20
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
#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/checksum.h"
#include "QF/dstring.h"
#include "QF/hash.h"
#include "QF/idparse.h"
#include "QF/info.h"
#include "QF/sys.h"
#include "QF/va.h"
#include "qw/bothdefs.h"
#include "qw/msg_ucmd.h"
#include "qw/protocol.h"
#include "qtv/include/client.h"
#include "qtv/include/connection.h"
#include "qtv/include/qtv.h"
#include "qtv/include/server.h"
int client_count;
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)
{
if (!cl->server) {
qtv_printf ("\"cmd list\" for a list of servers\n");
qtv_printf ("\"cmd connect <servername>\" to connect to a server\n");
} else {
Client_New (cl);
}
}
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 (0, "cmd spawn %i 0\n", cl->server->spawncount);
else
cmd = va (0, "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 + Info_CurrentSize(pl->info));
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, pl->ping);
MSG_WriteByte (msg, svc_updatepl);
MSG_WriteByte (msg, i);
MSG_WriteByte (msg, pl->pl);
MSG_WriteByte (msg, svc_updateentertime);
MSG_WriteByte (msg, i);
MSG_WriteFloat (msg, pl->time);
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)
{
int i;
if (Cmd_Argc () != 2) {
cl->spec_track = 0;
return;
}
i = atoi (Cmd_Argv (1));
if (i < 0 || i >= MAX_CLIENTS) {
cl->spec_track = 0;
qtv_printf ("Invalid client to track\n");
return;
}
cl->spec_track = 1 << i;
}
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.es.velocity, cl->state.es.velocity);
if (speed < 1) {
VectorZero (cl->state.es.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.es.velocity, newspeed, cl->state.es.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.es.velocity, wishdir);
addspeed = wishspeed - currentspeed;
if (addspeed <= 0)
return;
accelspeed = sv->movevars.accelerate * frametime * wishspeed;
if (accelspeed > addspeed)
accelspeed = addspeed;
VectorMultAdd (cl->state.es.velocity, accelspeed, wishdir,
cl->state.es.velocity);
VectorMultAdd (cl->state.es.origin, frametime, cl->state.es.velocity,
cl->state.es.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;
// 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
seq_hash = cl->netchan.incoming_sequence;
cl->delta_sequence = -1;
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) {
Sys_MaskPrintf (SYS_dev,
"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.es.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->es.flags & (PF_GIB | PF_DEAD))
| PF_MSEC | PF_COMMAND;
int qf_bits = 0;
if (pl->es.modelindex != sv->playermodel)
pflags |= PF_MODEL;
for (i = 0; i < 3; i++)
if (pl->es.velocity[i])
pflags |= PF_VELOCITY1 << i;
if (pl->es.effects & 0xff)
pflags |= PF_EFFECTS;
if (pl->es.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) {
// // send only 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->es.weaponframe)
// pflags |= PF_WEAPONFRAME;
// }
// if (client->spec_track && client->spec_track - 1 == j
// && pl->es.weaponframe)
// pflags |= PF_WEAPONFRAME;
MSG_WriteByte (msg, svc_playerinfo);
MSG_WriteByte (msg, num);
MSG_WriteShort (msg, pflags);
MSG_WriteCoordV (msg, &pl->es.origin[0]);//FIXME
pl->es.origin[3] = 1;
MSG_WriteByte (msg, pl->es.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->es.velocity[i]);
if (pflags & PF_MODEL)
MSG_WriteByte (msg, pl->es.modelindex);
if (pflags & PF_SKINNUM)
MSG_WriteByte (msg, pl->es.skinnum);
if (pflags & PF_EFFECTS)
MSG_WriteByte (msg, pl->es.effects);
if (pflags & PF_WEAPONFRAME)
MSG_WriteByte (msg, pl->es.weaponframe);
if (pflags & PF_QF) {
MSG_WriteByte (msg, qf_bits);
if (qf_bits & PF_ALPHA)
MSG_WriteByte (msg, pl->es.alpha);
if (qf_bits & PF_SCALE)
MSG_WriteByte (msg, pl->es.scale);
if (qf_bits & PF_EFFECTS2)
MSG_WriteByte (msg, pl->es.effects >> 8);
if (qf_bits & PF_GLOWSIZE)
MSG_WriteByte (msg, pl->es.glow_size);
if (qf_bits & PF_GLOWCOLOR)
MSG_WriteByte (msg, pl->es.glow_color);
if (qf_bits & PF_COLORMOD)
MSG_WriteByte (msg, pl->es.colormod);
if (qf_bits & PF_FRAME2)
MSG_WriteByte (msg, pl->es.frame >> 8);
}
}
#if 0
#define MAX_NAILS 32
edict_t *nails[MAX_NAILS];
int numnails;
int nailcount;
static void
emit_nails (sizebuf_t *msg, qboolean recorder)
{
byte *buf; // [48 bits] xyzpy 12 12 12 4 8
int n, p, x, y, z, yaw;
int bpn = recorder ? 7 : 6; // bytes per nail
edict_t *ent;
if (!numnails)
return;
buf = SZ_GetSpace (msg, numnails * bpn + 2);
*buf++ = recorder ? svc_nails2 : svc_nails;
*buf++ = numnails;
for (n = 0; n < numnails; n++) {
ent = nails[n];
if (recorder) {
if (!ent->colormap) {
if (!((++nailcount) & 255))
nailcount++;
ent->colormap = nailcount&255;
}
*buf++ = (byte)ent->colormap;
}
x = ((int) (ent->origin[0] + 4096 + 1) >> 1) & 4095;
y = ((int) (ent->origin[1] + 4096 + 1) >> 1) & 4095;
z = ((int) (ent->origin[2] + 4096 + 1) >> 1) & 4095;
p = (int) (ent->angles[0] * (16.0 / 360.0)) & 15;
yaw = (int) (ent->angles[1] * (256.0 / 360.0)) & 255;
*buf++ = x;
*buf++ = (x >> 8) | (y << 4);
*buf++ = (y >> 4);
*buf++ = z;
*buf++ = (z >> 8) | (p << 4);
*buf++ = yaw;
}
}
#endif
static void
write_delta (entity_state_t *from, entity_state_t *to, sizebuf_t *msg,
qboolean force)//, int stdver)
{
int bits, i;
float miss;
// send an update
bits = 0;
for (i = 0; i < 3; i++) {
miss = to->origin[i] - from->origin[i];
if (miss < -0.1 || miss > 0.1)
bits |= U_ORIGIN1 << i;
}
if (to->angles[0] != from->angles[0])
bits |= U_ANGLE1;
if (to->angles[1] != from->angles[1])
bits |= U_ANGLE2;
if (to->angles[2] != from->angles[2])
bits |= U_ANGLE3;
if (to->colormap != from->colormap)
bits |= U_COLORMAP;
if (to->skinnum != from->skinnum)
bits |= U_SKIN;
if (to->frame != from->frame)
bits |= U_FRAME;
if (to->effects != from->effects)
bits |= U_EFFECTS;
if (to->modelindex != from->modelindex)
bits |= U_MODEL;
// LordHavoc: cleaned up Endy's coding style, and added missing effects
// Ender (QSG - Begin)
#if 0
if (stdver > 1) {
if (to->alpha != from->alpha)
bits |= U_ALPHA;
if (to->scale != from->scale)
bits |= U_SCALE;
if (to->effects > 255)
bits |= U_EFFECTS2;
if (to->glow_size != from->glow_size)
bits |= U_GLOWSIZE;
if (to->glow_color != from->glow_color)
bits |= U_GLOWCOLOR;
if (to->colormod != from->colormod)
bits |= U_COLORMOD;
if (to->frame > 255)
bits |= U_EFFECTS2;
}
if (bits >= 16777216)
bits |= U_EXTEND2;
if (bits >= 65536)
bits |= U_EXTEND1;
#endif
if (bits & 511)
bits |= U_MOREBITS;
if (to->flags & U_SOLID)
bits |= U_SOLID;
if (!bits && !force)
return; // nothing to send!
i = to->number | (bits & ~511);
// if (i & U_REMOVE)
// Sys_Error ("U_REMOVE");
MSG_WriteShort (msg, i);
if (bits & U_MOREBITS)
MSG_WriteByte (msg, bits & 255);
// LordHavoc: cleaned up Endy's tabs
// Ender (QSG - Begin)
if (bits & U_EXTEND1)
MSG_WriteByte (msg, bits >> 16);
if (bits & U_EXTEND2)
MSG_WriteByte (msg, bits >> 24);
// Ender (QSG - End)
if (bits & U_MODEL)
MSG_WriteByte (msg, to->modelindex);
if (bits & U_FRAME)
MSG_WriteByte (msg, to->frame);
if (bits & U_COLORMAP)
MSG_WriteByte (msg, to->colormap);
if (bits & U_SKIN)
MSG_WriteByte (msg, to->skinnum);
if (bits & U_EFFECTS)
MSG_WriteByte (msg, to->effects);
if (bits & U_ORIGIN1)
MSG_WriteCoord (msg, to->origin[0]);
if (bits & U_ANGLE1)
MSG_WriteAngle (msg, to->angles[0]);
if (bits & U_ORIGIN2)
MSG_WriteCoord (msg, to->origin[1]);
if (bits & U_ANGLE2)
MSG_WriteAngle (msg, to->angles[1]);
if (bits & U_ORIGIN3)
MSG_WriteCoord (msg, to->origin[2]);
if (bits & U_ANGLE3)
MSG_WriteAngle (msg, to->angles[2]);
// LordHavoc: cleaned up Endy's tabs, rearranged bytes, and implemented
// missing effects
// Ender (QSG - Begin)
if (bits & U_ALPHA)
MSG_WriteByte (msg, to->alpha);
if (bits & U_SCALE)
MSG_WriteByte (msg, to->scale);
if (bits & U_EFFECTS2)
MSG_WriteByte (msg, (to->effects >> 8));
if (bits & U_GLOWSIZE)
MSG_WriteByte (msg, to->glow_size);
if (bits & U_GLOWCOLOR)
MSG_WriteByte (msg, to->glow_color);
if (bits & U_COLORMOD)
MSG_WriteByte (msg, to->colormod);
if (bits & U_FRAME2)
MSG_WriteByte (msg, (to->frame >> 8));
// Ender (QSG - End)
}
static void
emit_entities (client_t *client, packet_entities_t *to, sizebuf_t *msg)
{
int newindex, oldindex, newnum, oldnum, oldmax;
entity_state_t *ent;
frame_t *fromframe;
packet_entities_t *from;
server_t *sv = client->server;
// this is the frame that we are going to delta update from
if (client->delta_sequence != -1) {
fromframe = &client->frames[client->delta_sequence & UPDATE_MASK];
from = &fromframe->entities;
oldmax = from->num_entities;
MSG_WriteByte (msg, svc_deltapacketentities);
MSG_WriteByte (msg, client->delta_sequence);
} else {
oldmax = 0; // no delta update
from = NULL;
MSG_WriteByte (msg, svc_packetentities);
}
newindex = 0;
oldindex = 0;
while (newindex < to->num_entities || oldindex < oldmax) {
newnum = newindex >= to->num_entities ? 9999 :
to->entities[newindex].number;
oldnum = oldindex >= oldmax ? 9999 : from->entities[oldindex].number;
if (newnum == oldnum) { // delta update from old position
write_delta (&from->entities[oldindex], &to->entities[newindex],
msg, false);//, client->stdver);
oldindex++;
newindex++;
continue;
}
if (newnum < oldnum) {
// this is a new entity, send it from the baseline
ent = sv->baselines + newnum;
write_delta (ent, &to->entities[newindex], msg, true);//,
//client->stdver);
newindex++;
continue;
}
if (newnum > oldnum) { // the old entity isn't present in
// the new message
MSG_WriteShort (msg, oldnum | U_REMOVE);
oldindex++;
continue;
}
}
MSG_WriteShort (msg, 0); // end of packetentities
}
static void
write_entities (client_t *client, sizebuf_t *msg)
{
//byte *pvs = 0;
//int i;
int e;
//vec3_t org;
frame_t *frame;
entity_state_t *ent;
entity_state_t *state;
packet_entities_t *pack;
server_t *sv = client->server;
// this is the frame we are creating
frame = &client->frames[client->netchan.incoming_sequence & UPDATE_MASK];
// find the client's PVS
//clent = client->edict;
//VectorAdd (SVvector (clent, origin), SVvector (clent, view_ofs), org);
//pvs = SV_FatPVS (org);
// put other visible entities into either a packet_entities or a nails
// message
pack = &frame->entities;
pack->num_entities = 0;
//numnails = 0;
for (e = MAX_CLIENTS + 1, ent = sv->entities + e; e < MAX_SV_ENTITIES;
e++, ent++) {
if (!sv->ent_valid[e])
continue;
if (ent->number && ent->number != e)
qtv_printf ("%d %d\n", e, ent->number);
#if 0
if (pvs) {
// ignore if not touching a PV leaf
for (i = 0; i < ent->num_leafs; i++)
if (pvs[ent->leafnums[i] >> 3] & (1 << (ent->leafnums[i] & 7)))
break;
if (i == ent->num_leafs)
continue; // not visible
}
#endif
// if (SV_AddNailUpdate (ent))
// continue; // added to the special update list
// add to the packetentities
if (pack->num_entities == MAX_PACKET_ENTITIES) {
qtv_printf ("mpe overflow\n");
continue; // all full
}
state = &pack->entities[pack->num_entities];
pack->num_entities++;
*state = *ent;
state->flags = 0;
}
// encode the packet entities as a delta from the
// last packetentities acknowledged by the client
emit_entities (client, pack, msg);
// now add the specialized nail update
//emit_nails (msg, 0);
}
static void
write_players (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);
}
}
void
Client_SendMessages (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_players (cl, &msg);
write_player (31, &cl->state, cl->server, &msg);
write_entities (cl, &msg);
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;
int i;
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));
client_count++;
Netchan_Setup (&cl->netchan, con->address, qport, NC_QPORT_READ);
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;
for (i = 0; i < UPDATE_BACKUP; i++)
cl->frames[i].entities.entities = cl->packet_entities[i];
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 (const 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, 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 (0, "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);
client_count--;
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) {
Client_SendMessages (cl);
cl->send_message = false;
}
c = &(*c)->clnext;
}
}