/* client.c quakeworld client processing Copyright (C) 2004 Bill Currie 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 #endif #ifdef HAVE_STRINGS_H # include #endif #include #include #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 \" 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; bool 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, bool 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, bool 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) { auto brush = &sv->worldmodel->brush; 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, brush)); } return; } mnode_t *node = 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; } }