quakeforge/qtv/source/client.c
Bill Currie 7240d2dc80 [model] Move plane info into mnode_t, and visframe out
The main goal was to get visframe out of mnode_t to make it thread-safe
(each thread can have its own visframe array), but moving the plane info
into mnode_t made for better data access patters when traversing the bsp
tree as the plane is right there with the child indices. Nicely, the
size of mnode_t is the same as before (64 bytes due to alignment), with
4 bytes wasted.

Performance-wise, there seems to be very little difference. Maybe
slightly slower.

The unfortunate thing about the change is the plane distance is negated,
possibly leading to some confusion, particularly since the box and
sphere culling functions were affected. However, this is so point-plane
distance calculations can be done with a single 4d dot product.
2022-05-22 12:41:23 +09:00

1322 lines
32 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/set.h"
#include "QF/sys.h"
#include "QF/va.h"
#include "QF/simd/vec4f.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, (vec_t*)&pl->es.origin);//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
add_to_fat_pvs (vec4f_t org, int node_id, server_t *sv)
{
while (1) {
// if this is a leaf, accumulate the pvs bits
if (node_id < 0) {
mleaf_t *leaf = sv->worldmodel->brush.leafs + ~node_id;
if (leaf->contents != CONTENTS_SOLID) {
set_union (sv->fatpvs, Mod_LeafPVS (leaf, sv->worldmodel));
}
return;
}
mnode_t *node = sv->worldmodel->brush.nodes + node_id;
float d = dotf (node->plane, org)[0];
if (d > 8)
node_id = node->children[0];
else if (d < -8)
node_id = node->children[1];
else { // go down both
add_to_fat_pvs (org, node->children[0], sv);
node_id = node->children[1];
}
}
}
static set_t *
fat_pvs (vec4f_t org, server_t *sv)
{
if (!sv->fatpvs) {
sv->fatpvs = set_new_size (sv->worldmodel->brush.visleafs);
}
set_expand (sv->fatpvs, sv->worldmodel->brush.visleafs);
set_empty (sv->fatpvs);
add_to_fat_pvs (org, 0, sv);
return sv->fatpvs;
}
static void
write_entities (client_t *client, sizebuf_t *msg)
{
set_t *pvs = 0;
int e;
vec4f_t org;
frame_t *frame;
qtv_entity_t *ent;
qtv_leaf_t *el;
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
org = client->state.es.origin;
org[2] += 22; //XXX standard spectator view offset
pvs = fat_pvs (org, sv);
// 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->e.number && ent->e.number != e)
qtv_printf ("%d %d\n", e, ent->e.number);
if (pvs) {
// ignore if not touching a PV leaf
for (el = ent->leafs; el; el = el->next) {
if (set_is_member (pvs, el->num))
break;
}
if (!el)
continue; // not visible
}
// 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->e;
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;
}
}