From b84125ed0d5fc6922a9ea18b7762b489e377175d Mon Sep 17 00:00:00 2001 From: havoc Date: Fri, 24 Feb 2006 06:04:40 +0000 Subject: [PATCH] beginnings of qw protocol support git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@6026 d7cf8633-e32d-0410-b094-e92efae38249 --- cl_input.c | 2 +- cl_main.c | 12 +- cl_parse.c | 62 ++++++++- client.h | 22 ++++ common.h | 2 + cvar.c | 19 +++ cvar.h | 6 +- host.c | 6 +- host_cmd.c | 189 ++++++++++++++++++++++++++- netconn.c | 375 +++++++++++++++++++++++++++++++++++++++-------------- netconn.h | 51 +++++++- protocol.c | 1 + protocol.h | 119 +++++++++++++++++ quakedef.h | 5 + server.h | 2 + sv_main.c | 5 +- 16 files changed, 759 insertions(+), 119 deletions(-) diff --git a/cl_input.c b/cl_input.c index c38a8bff..c6eb720b 100644 --- a/cl_input.c +++ b/cl_input.c @@ -1023,7 +1023,7 @@ void CL_SendMove(void) } // send the reliable message (forwarded commands) if there is one - NetConn_SendUnreliableMessage(cls.netcon, &buf); + NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol); if (cls.netcon->message.overflowed) { diff --git a/cl_main.c b/cl_main.c index d008691e..83869387 100644 --- a/cl_main.c +++ b/cl_main.c @@ -66,6 +66,8 @@ cvar_t cl_beams_lightatend = {CVAR_SAVE, "cl_beams_lightatend", "0","make a ligh cvar_t cl_noplayershadow = {CVAR_SAVE, "cl_noplayershadow", "0","hide player shadow"}; +cvar_t qport = {0, "qport", "0", "identification key for playing on qw servers (allows you to maintain a connection to a quakeworld server even if your port changes)"}; + cvar_t cl_prydoncursor = {0, "cl_prydoncursor", "0", "enables a mouse pointer which is able to click on entities in the world, useful for point and click mods, see PRYDON_CLIENTCURSOR extension in dpextensions.qc"}; cvar_t cl_deathnoviewmodel = {0, "cl_deathnoviewmodel", "1", "hides gun model when dead"}; @@ -312,9 +314,9 @@ void CL_Disconnect(void) buf.data = bufdata; buf.maxsize = sizeof(bufdata); MSG_WriteByte(&buf, clc_disconnect); - NetConn_SendUnreliableMessage(cls.netcon, &buf); - NetConn_SendUnreliableMessage(cls.netcon, &buf); - NetConn_SendUnreliableMessage(cls.netcon, &buf); + NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol); + NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol); + NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol); NetConn_Close(cls.netcon); cls.netcon = NULL; } @@ -1677,6 +1679,10 @@ void CL_Init (void) Cvar_RegisterVariable(&cl_deathnoviewmodel); + // for QW connections + Cvar_RegisterVariable(&qport); + Cvar_SetValueQuick(&qport, (rand() * RAND_MAX + rand()) & 0xffff); + Cmd_AddCommand("timerefresh", CL_TimeRefresh_f, "turn quickly and print rendering statistcs"); CL_Parse_Init(); diff --git a/cl_parse.c b/cl_parse.c index 44d20b23..43307fb9 100644 --- a/cl_parse.c +++ b/cl_parse.c @@ -95,6 +95,64 @@ char *svc_strings[128] = "svc_spawnstaticsound2", // 59 // [coord3] [short] samp [byte] vol [byte] aten }; +char *qw_svc_strings[128] = +{ + "qw_svc_bad", // 0 + "qw_svc_nop", // 1 + "qw_svc_disconnect", // 2 + "qw_svc_updatestat", // 3 // [byte] [byte] + "", // 4 + "qw_svc_setview", // 5 // [short] entity number + "qw_svc_sound", // 6 // + "", // 7 + "qw_svc_print", // 8 // [byte] id [string] null terminated string + "qw_svc_stufftext", // 9 // [string] stuffed into client's console buffer + "qw_svc_setangle", // 10 // [angle3] set the view angle to this absolute value + "qw_svc_serverdata", // 11 // [long] protocol ... + "qw_svc_lightstyle", // 12 // [byte] [string] + "", // 13 + "qw_svc_updatefrags", // 14 // [byte] [short] + "", // 15 + "qw_svc_stopsound", // 16 // + "", // 17 + "", // 18 + "qw_svc_damage", // 19 + "qw_svc_spawnstatic", // 20 + "", // 21 + "qw_svc_spawnbaseline", // 22 + "qw_svc_temp_entity", // 23 // variable + "qw_svc_setpause", // 24 // [byte] on / off + "", // 25 + "qw_svc_centerprint", // 26 // [string] to put in center of the screen + "qw_svc_killedmonster", // 27 + "qw_svc_foundsecret", // 28 + "qw_svc_spawnstaticsound", // 29 // [coord3] [byte] samp [byte] vol [byte] aten + "qw_svc_intermission", // 30 // [vec3_t] origin [vec3_t] angle + "qw_svc_finale", // 31 // [string] text + "qw_svc_cdtrack", // 32 // [byte] track + "qw_svc_sellscreen", // 33 + "qw_svc_smallkick", // 34 // set client punchangle to 2 + "qw_svc_bigkick", // 35 // set client punchangle to 4 + "qw_svc_updateping", // 36 // [byte] [short] + "qw_svc_updateentertime", // 37 // [byte] [float] + "qw_svc_updatestatlong", // 38 // [byte] [long] + "qw_svc_muzzleflash", // 39 // [short] entity + "qw_svc_updateuserinfo", // 40 // [byte] slot [long] uid + "qw_svc_download", // 41 // [short] size [size bytes] + "qw_svc_playerinfo", // 42 // variable + "qw_svc_nails", // 43 // [byte] num [48 bits] xyzpy 12 12 12 4 8 + "qw_svc_chokecount", // 44 // [byte] packets choked + "qw_svc_modellist", // 45 // [strings] + "qw_svc_soundlist", // 46 // [strings] + "qw_svc_packetentities", // 47 // [...] + "qw_svc_deltapacketentities", // 48 // [...] + "qw_svc_maxspeed", // 49 // maxspeed change, for prediction + "qw_svc_entgravity", // 50 // gravity change, for prediction + "qw_svc_setinfo", // 51 // setinfo on a client + "qw_svc_serverinfo", // 52 // serverinfo + "qw_svc_updatepl", // 53 // [byte] [byte] +}; + //============================================================================= cvar_t demo_nehahra = {0, "demo_nehahra", "0", "reads all quake demos as nehahra movie protocol"}; @@ -179,7 +237,7 @@ void CL_KeepaliveMessage (void) sizebuf_t old; // no need if server is local and definitely not if this is a demo - if (sv.active || !cls.netcon) + if (sv.active || !cls.netcon || cls.protocol == PROTOCOL_QUAKEWORLD) return; // read messages from server, should just be nops @@ -207,7 +265,7 @@ void CL_KeepaliveMessage (void) msg.data = buf; msg.maxsize = sizeof(buf); MSG_WriteChar(&msg, svc_nop); - NetConn_SendUnreliableMessage(cls.netcon, &msg); + NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol); } } diff --git a/client.h b/client.h index 9dbe0686..4ae120e6 100644 --- a/client.h +++ b/client.h @@ -369,6 +369,14 @@ typedef struct scoreboard_s char name[MAX_SCOREBOARDNAME]; int frags; int colors; // two 4 bit fields + // QW fields: + int userid; + char userinfo[MAX_USERINFO_STRING]; + float entertime; + int ping; + int packetloss; + int spectator; + // TODO: QW skin support } scoreboard_t; typedef struct cshift_s @@ -412,6 +420,9 @@ typedef struct client_static_s { cactive_t state; + // value of "qport" cvar at time of connection + int qport; + // demo loop control // -1 = don't play demos int demonum; @@ -455,6 +466,13 @@ typedef struct client_static_s int signon; // network connection netconn_t *netcon; + + // quakeworld stuff below + + // user infostring + // this normally contains the following keys in quakeworld: + // password spectator name team skin topcolor bottomcolor rate noaim msg *ver *ip + char userinfo[MAX_USERINFO_STRING]; } client_static_t; @@ -640,6 +658,9 @@ typedef struct client_state_s // [cl.maxclients] scoreboard_t *scores; + // local copy of the server infostring + char serverinfo[MAX_SERVERINFO_STRING]; + // entity database stuff // latest received entity frame numbers #define LATESTFRAMENUMS 3 @@ -830,6 +851,7 @@ void CL_Parse_Init(void); void CL_Parse_Shutdown(void); void CL_ParseServerMessage(void); void CL_Parse_DumpPacket(void); +extern cvar_t qport; // // view diff --git a/common.h b/common.h index b1cba03c..3bba8dd2 100644 --- a/common.h +++ b/common.h @@ -141,6 +141,7 @@ typedef enum protocolversion_e PROTOCOL_QUAKEDP, // darkplaces extended quake protocol (used by TomazQuake and others), backwards compatible as long as no extended features are used PROTOCOL_NEHAHRAMOVIE, // Nehahra movie protocol, a big nasty hack dating back to early days of the Quake Standards Group (but only ever used by neh_gl.exe), this is potentially backwards compatible with quake protocol as long as no extended features are used (but in actuality the neh_gl.exe which wrote this protocol ALWAYS wrote the extended information) PROTOCOL_QUAKE, // quake (aka netquake/normalquake/nq) protocol + PROTOCOL_QUAKEWORLD, // quakeworld protocol } protocolversion_t; @@ -283,6 +284,7 @@ char *SearchInfostring(const char *infostring, const char *key); void InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength); void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, const char *value); +void InfoString_Print(char *buffer); // strlcat and strlcpy, from OpenBSD // Most (all?) BSDs already have them diff --git a/cvar.c b/cvar.c index 990ebdb8..f3b9e70e 100644 --- a/cvar.c +++ b/cvar.c @@ -229,6 +229,25 @@ void Cvar_SetQuick_Internal (cvar_t *var, const char *value) var->integer = (int) var->value; if ((var->flags & CVAR_NOTIFY) && changed && sv.active) SV_BroadcastPrintf("\"%s\" changed to \"%s\"\n", var->name, var->string); +#if 0 + // TODO: add infostring support to the server? + if ((var->flags & CVAR_SERVERINFO) && changed && sv.active) + { + InfoString_SetValue(svs.serverinfo, sizeof(svs.serverinfo), var->name, var->string); + if (sv.active) + { + MSG_WriteByte (&sv.reliable_datagram, svc_serverinfostring); + MSG_WriteString (&sv.reliable_datagram, var->name); + MSG_WriteString (&sv.reliable_datagram, var->string); + } + } +#endif + if ((var->flags & CVAR_USERINFO) && changed && cls.state != ca_dedicated) + { + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), var->name, var->string); + if (cls.state == ca_connected) + Cmd_ForwardStringToServer(va("setinfo \"%s\" \"%s\"\n", var->name, var->string)); + } } void Cvar_SetQuick (cvar_t *var, const char *value) diff --git a/cvar.h b/cvar.h index fbf3b961..d2b57158 100644 --- a/cvar.h +++ b/cvar.h @@ -60,9 +60,11 @@ interface from being ambiguous. #define CVAR_SAVE 1 #define CVAR_NOTIFY 2 -#define CVAR_READONLY 4 +#define CVAR_READONLY 4 +#define CVAR_SERVERINFO 8 +#define CVAR_USERINFO 16 // used to determine if flags is valid -#define CVAR_MAXFLAGSVAL 7 +#define CVAR_MAXFLAGSVAL 31 // for internal use only! #define CVAR_DEFAULTSET (1<<30) #define CVAR_ALLOCATED (1<<31) diff --git a/host.c b/host.c index 6edefc97..34d1417a 100644 --- a/host.c +++ b/host.c @@ -419,9 +419,9 @@ void SV_DropClient(qboolean crash) buf.data = bufdata; buf.maxsize = sizeof(bufdata); MSG_WriteByte(&buf, svc_disconnect); - NetConn_SendUnreliableMessage(host_client->netconnection, &buf); - NetConn_SendUnreliableMessage(host_client->netconnection, &buf); - NetConn_SendUnreliableMessage(host_client->netconnection, &buf); + NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol); + NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol); + NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol); } // break the net connection NetConn_Close(host_client->netconnection); diff --git a/host_cmd.c b/host_cmd.c index 98b7d090..865ecb02 100644 --- a/host_cmd.c +++ b/host_cmd.c @@ -758,6 +758,7 @@ void Host_Name_f (void) if (cmd_source == src_command) { Cvar_Set ("_cl_name", newName); + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "name", newName); if (cls.state == ca_connected) Cmd_ForwardToServer (); return; @@ -817,6 +818,7 @@ void Host_Playermodel_f (void) if (cmd_source == src_command) { Cvar_Set ("_cl_playermodel", newPath); + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "playermodel", newPath); if (cls.state == ca_connected) Cmd_ForwardToServer (); return; @@ -876,6 +878,7 @@ void Host_Playerskin_f (void) if (cmd_source == src_command) { Cvar_Set ("_cl_playerskin", newPath); + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "playerskin", newPath); if (cls.state == ca_connected) Cmd_ForwardToServer (); return; @@ -1093,11 +1096,16 @@ void Host_Color_f(void) if (cmd_source == src_command) { Cvar_SetValue ("_cl_color", playercolor); - if (cls.state == ca_connected) + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "topcolor", va("%i", top)); + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "bottomcolor", va("%i", bottom)); + if (cls.state == ca_connected && cls.protocol != PROTOCOL_QUAKEWORLD) Cmd_ForwardToServer (); return; } + if (cls.protocol == PROTOCOL_QUAKEWORLD) + return; + if (host_client->edict && (f = PRVM_ED_FindFunction ("SV_ChangeTeam")) && (SV_ChangeTeam = (func_t)(f - prog->functions))) { Con_DPrint("Calling SV_ChangeTeam\n"); @@ -1144,6 +1152,7 @@ void Host_Rate_f(void) if (cmd_source == src_command) { Cvar_SetValue ("_cl_rate", bound(NET_MINRATE, rate, NET_MAXRATE)); + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "rate", va("%i", rate)); if (cls.state == ca_connected) Cmd_ForwardToServer (); return; @@ -2010,6 +2019,177 @@ void Host_Rcon_f (void) // credit: taken from QuakeWorld } } +/* +==================== +Host_User_f + +user + +Dump userdata / masterdata for a user +==================== +*/ +void Host_User_f (void) // credit: taken from QuakeWorld +{ + int uid; + int i; + + if (Cmd_Argc() != 2) + { + Con_Printf ("Usage: user \n"); + return; + } + + uid = atoi(Cmd_Argv(1)); + + for (i = 0;i < cl.maxclients;i++) + { + if (!cl.scores[i].name[0]) + continue; + if (cl.scores[i].userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1))) + { + InfoString_Print(cl.scores[i].userinfo); + return; + } + } + Con_Printf ("User not in server.\n"); +} + +/* +==================== +Host_Users_f + +Dump userids for all current players +==================== +*/ +void Host_Users_f (void) // credit: taken from QuakeWorld +{ + int i; + int c; + + c = 0; + Con_Printf ("userid frags name\n"); + Con_Printf ("------ ----- ----\n"); + for (i = 0;i < cl.maxclients;i++) + { + if (cl.scores[i].name[0]) + { + Con_Printf ("%6i %4i %s\n", cl.scores[i].userid, cl.scores[i].frags, cl.scores[i].name); + c++; + } + } + + Con_Printf ("%i total users\n", c); +} + +/* +================== +Host_FullServerinfo_f + +Sent by server when serverinfo changes +================== +*/ +// TODO: shouldn't this be a cvar instead? +void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld +{ + if (Cmd_Argc() != 2) + { + Con_Printf ("usage: fullserverinfo \n"); + return; + } + + strlcpy (cl.serverinfo, Cmd_Argv(1), sizeof(cl.serverinfo)); +} + +/* +================== +Host_FullInfo_f + +Allow clients to change userinfo +================== +Casey was here :) +*/ +void Host_FullInfo_f (void) // credit: taken from QuakeWorld +{ + char key[512]; + char value[512]; + char *o; + const char *s; + + if (Cmd_Argc() != 2) + { + Con_Printf ("fullinfo \n"); + return; + } + + s = Cmd_Argv(1); + if (*s == '\\') + s++; + while (*s) + { + o = key; + while (*s && *s != '\\') + *o++ = *s++; + *o = 0; + + if (!*s) + { + Con_Printf ("MISSING VALUE\n"); + return; + } + + o = value; + s++; + while (*s && *s != '\\') + *o++ = *s++; + *o = 0; + + if (*s) + s++; + + if (!strcasecmp(key, pmodel_name) || !strcasecmp(key, emodel_name)) + continue; + + if (key[0] == '*') + { + Con_Printf("Can't set star-key \"%s\" to \"%s\"\n", key, value); + continue; + } + + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), key, value); + } +} + +/* +================== +CL_SetInfo_f + +Allow clients to change userinfo +================== +*/ +void Host_SetInfo_f (void) // credit: taken from QuakeWorld +{ + if (Cmd_Argc() == 1) + { + InfoString_Print(cls.userinfo); + return; + } + if (Cmd_Argc() != 3) + { + Con_Printf ("usage: setinfo [ ]\n"); + return; + } + if (!strcasecmp(Cmd_Argv(1), pmodel_name) || !strcasecmp(Cmd_Argv(1), emodel_name)) + return; + if (Cmd_Argv(1)[0] == '*') + { + Con_Printf("Can't set star-key \"%s\" to \"%s\"\n", Cmd_Argv(1), Cmd_Argv(2)); + return; + } + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), Cmd_Argv(1), Cmd_Argv(2)); + if (cls.state == ca_connected) + Cmd_ForwardToServer (); +} + /* ==================== Host_Packet_f @@ -2073,6 +2253,8 @@ Host_InitCommands */ void Host_InitCommands (void) { + strcpy(cls.userinfo, "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\*ver\\dp"); + Cmd_AddCommand ("status", Host_Status_f, "print server status information"); Cmd_AddCommand ("quit", Host_Quit_f, "quit the game"); if (gamemode == GAME_NEHAHRA) @@ -2144,6 +2326,11 @@ void Host_InitCommands (void) Cvar_RegisterVariable (&rcon_password); Cvar_RegisterVariable (&rcon_address); Cmd_AddCommand ("rcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's)"); + Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard"); + Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard"); + Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string"); + Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo"); + Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo"); Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string"); Cvar_RegisterVariable(&sv_cheats); diff --git a/netconn.c b/netconn.c index c729f2b2..0ce9c745 100755 --- a/netconn.c +++ b/netconn.c @@ -432,112 +432,170 @@ int NetConn_WriteString(lhnetsocket_t *mysocket, const char *string, const lhnet return NetConn_Write(mysocket, string, (int)strlen(string), peeraddress); } -int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data) +int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol) { - unsigned int packetLen; - unsigned int dataLen; - unsigned int eom; - unsigned int *header; - - // if a reliable message fragment has been lost, send it again - if (conn->sendMessageLength && (realtime - conn->lastSendTime) > 1.0) + if (protocol == PROTOCOL_QUAKEWORLD) { - if (conn->sendMessageLength <= MAX_PACKETFRAGMENT) + int packetLen; + qboolean sendreliable; + + if (data->cursize == 0 && conn->message.cursize == 0) { - dataLen = conn->sendMessageLength; - eom = NETFLAG_EOM; + Con_Printf ("Datagram_SendUnreliableMessage: zero length message\n"); + return -1; } - else + + sendreliable = false; + // if the remote side dropped the last reliable message, resend it + if (conn->qw.incoming_acknowledged > conn->qw.last_reliable_sequence && conn->qw.incoming_reliable_acknowledged != conn->qw.reliable_sequence) + sendreliable = true; + // if the reliable transmit buffer is empty, copy the current message out + if (!conn->sendMessageLength && conn->message.cursize) { - dataLen = MAX_PACKETFRAGMENT; - eom = 0; + memcpy (conn->sendMessage, conn->message.data, conn->message.cursize); + conn->sendMessageLength = conn->message.cursize; + SZ_Clear(&conn->message); // clear the message buffer + conn->qw.reliable_sequence ^= 1; + sendreliable = true; } - - packetLen = NET_HEADERSIZE + dataLen; - - header = (unsigned int *)sendbuffer; - header[0] = BigLong(packetLen | (NETFLAG_DATA | eom)); - header[1] = BigLong(conn->sendSequence - 1); - memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); - - if (NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress) == (int)packetLen) + // outgoing unreliable packet number, and outgoing reliable packet number (0 or 1) + *((int *)(sendbuffer + 0)) = LittleLong(conn->qw.outgoing_sequence | (sendreliable<<31)); + // last received unreliable packet number, and last received reliable packet number (0 or 1) + *((int *)(sendbuffer + 4)) = LittleLong(conn->qw.incoming_sequence | (conn->qw.incoming_reliable_sequence<<31)); + packetLen = 8; + // client sends qport in every packet + if (conn == cls.netcon) { - conn->lastSendTime = realtime; - packetsReSent++; + *((short *)(sendbuffer + 8)) = LittleShort(cls.qport); + packetLen += 2; } - } - - // if we have a new reliable message to send, do so - if (!conn->sendMessageLength && conn->message.cursize) - { - if (conn->message.cursize > (int)sizeof(conn->sendMessage)) + if (packetLen + (sendreliable ? conn->sendMessageLength : 0) + data->cursize > (int)sizeof(sendbuffer)) { - Con_Printf("NetConn_SendUnreliableMessage: reliable message too big (%u > %u)\n", conn->message.cursize, sizeof(conn->sendMessage)); - conn->message.overflowed = true; + Con_Printf ("NetConn_SendUnreliableMessage: reliable message too big %u\n", data->cursize); return -1; } - - if (developer_networking.integer && conn == cls.netcon) + if (sendreliable) { - Con_Print("client sending reliable message to server:\n"); - SZ_HexDumpToConsole(&conn->message); + memcpy(sendbuffer + packetLen, conn->sendMessage, conn->sendMessageLength); + packetLen += conn->sendMessageLength; } + memcpy(sendbuffer + packetLen, data->data, data->cursize); + packetLen += data->cursize; + conn->qw.outgoing_sequence++; - memcpy(conn->sendMessage, conn->message.data, conn->message.cursize); - conn->sendMessageLength = conn->message.cursize; - SZ_Clear(&conn->message); + NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress); + + packetsSent++; + unreliableMessagesSent++; + return 0; + } + else + { + unsigned int packetLen; + unsigned int dataLen; + unsigned int eom; + unsigned int *header; - if (conn->sendMessageLength <= MAX_PACKETFRAGMENT) + // if a reliable message fragment has been lost, send it again + if (conn->sendMessageLength && (realtime - conn->lastSendTime) > 1.0) { - dataLen = conn->sendMessageLength; - eom = NETFLAG_EOM; + if (conn->sendMessageLength <= MAX_PACKETFRAGMENT) + { + dataLen = conn->sendMessageLength; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_PACKETFRAGMENT; + eom = 0; + } + + packetLen = NET_HEADERSIZE + dataLen; + + header = (unsigned int *)sendbuffer; + header[0] = BigLong(packetLen | (NETFLAG_DATA | eom)); + header[1] = BigLong(conn->nq.sendSequence - 1); + memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); + + if (NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress) == (int)packetLen) + { + conn->lastSendTime = realtime; + packetsReSent++; + } } - else + + // if we have a new reliable message to send, do so + if (!conn->sendMessageLength && conn->message.cursize) { - dataLen = MAX_PACKETFRAGMENT; - eom = 0; - } + if (conn->message.cursize > (int)sizeof(conn->sendMessage)) + { + Con_Printf("NetConn_SendUnreliableMessage: reliable message too big (%u > %u)\n", conn->message.cursize, sizeof(conn->sendMessage)); + conn->message.overflowed = true; + return -1; + } - packetLen = NET_HEADERSIZE + dataLen; + if (developer_networking.integer && conn == cls.netcon) + { + Con_Print("client sending reliable message to server:\n"); + SZ_HexDumpToConsole(&conn->message); + } - header = (unsigned int *)sendbuffer; - header[0] = BigLong(packetLen | (NETFLAG_DATA | eom)); - header[1] = BigLong(conn->sendSequence); - memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); + memcpy(conn->sendMessage, conn->message.data, conn->message.cursize); + conn->sendMessageLength = conn->message.cursize; + SZ_Clear(&conn->message); - conn->sendSequence++; + if (conn->sendMessageLength <= MAX_PACKETFRAGMENT) + { + dataLen = conn->sendMessageLength; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_PACKETFRAGMENT; + eom = 0; + } - NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress); + packetLen = NET_HEADERSIZE + dataLen; - conn->lastSendTime = realtime; - packetsSent++; - reliableMessagesSent++; - } + header = (unsigned int *)sendbuffer; + header[0] = BigLong(packetLen | (NETFLAG_DATA | eom)); + header[1] = BigLong(conn->nq.sendSequence); + memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); - // if we have an unreliable message to send, do so - if (data->cursize) - { - packetLen = NET_HEADERSIZE + data->cursize; + conn->nq.sendSequence++; - if (packetLen > (int)sizeof(sendbuffer)) - { - Con_Printf("NetConn_SendUnreliableMessage: message too big %u\n", data->cursize); - return -1; + NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress); + + conn->lastSendTime = realtime; + packetsSent++; + reliableMessagesSent++; } - header = (unsigned int *)sendbuffer; - header[0] = BigLong(packetLen | NETFLAG_UNRELIABLE); - header[1] = BigLong(conn->unreliableSendSequence); - memcpy(sendbuffer + NET_HEADERSIZE, data->data, data->cursize); + // if we have an unreliable message to send, do so + if (data->cursize) + { + packetLen = NET_HEADERSIZE + data->cursize; - conn->unreliableSendSequence++; + if (packetLen > (int)sizeof(sendbuffer)) + { + Con_Printf("NetConn_SendUnreliableMessage: message too big %u\n", data->cursize); + return -1; + } - NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress); + header = (unsigned int *)sendbuffer; + header[0] = BigLong(packetLen | NETFLAG_UNRELIABLE); + header[1] = BigLong(conn->nq.unreliableSendSequence); + memcpy(sendbuffer + NET_HEADERSIZE, data->data, data->cursize); - packetsSent++; - unreliableMessagesSent++; + conn->nq.unreliableSendSequence++; + + NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress); + + packetsSent++; + unreliableMessagesSent++; + } + return 0; } - return 0; } void NetConn_CloseClientPorts(void) @@ -736,15 +794,76 @@ void NetConn_UpdateSockets(void) } } -static int NetConn_ReceivedMessage(netconn_t *conn, unsigned char *data, int length) +static int NetConn_ReceivedMessage(netconn_t *conn, unsigned char *data, int length, protocolversion_t protocol) { - unsigned int count; - unsigned int flags; - unsigned int sequence; - int qlength; + if (length < 8) + return 0; - if (length >= 8) + if (protocol == PROTOCOL_QUAKEWORLD) + { + int sequence, sequence_ack; + int reliable_ack, reliable_message; + int count; + int qport; + + sequence = LittleLong(*((int *)(data + 0))); + sequence_ack = LittleLong(*((int *)(data + 4))); + data += 8; + length -= 8; + + if (conn != cls.netcon) + { + // server only + if (length < 2) + return 0; + // TODO: use qport to identify that this client really is who they say they are? (and elsewhere in the code to identify the connection without a port match?) + qport = LittleShort(*((int *)(data + 8))); + data += 2; + length -= 2; + } + + packetsReceived++; + reliable_message = sequence >> 31; + reliable_ack = sequence_ack >> 31; + sequence &= ~(1<<31); + sequence_ack &= ~(1<<31); + if (sequence <= conn->qw.incoming_sequence) + { + Con_DPrint("Got a stale datagram\n"); + return 0; + } + count = sequence - (conn->qw.incoming_sequence + 1); + if (count > 0) + { + droppedDatagrams += count; + Con_DPrintf("Dropped %u datagram(s)\n", count); + } + if (reliable_ack == conn->qw.reliable_sequence) + { + // received, now we will be able to send another reliable message + conn->sendMessageLength = 0; + reliableMessagesReceived++; + } + conn->qw.incoming_sequence = sequence; + conn->qw.incoming_acknowledged = sequence_ack; + conn->qw.incoming_reliable_acknowledged = reliable_ack; + if (reliable_message) + conn->qw.incoming_reliable_sequence ^= 1; + conn->lastMessageTime = realtime; + conn->timeout = realtime + net_messagetimeout.value; + unreliableMessagesReceived++; + SZ_Clear(&net_message); + SZ_Write(&net_message, data, length); + MSG_BeginReading(); + return 2; + } + else { + unsigned int count; + unsigned int flags; + unsigned int sequence; + int qlength; + qlength = (unsigned int)BigLong(((int *)data)[0]); flags = qlength & ~NETFLAG_LENGTH_MASK; qlength &= NETFLAG_LENGTH_MASK; @@ -757,15 +876,15 @@ static int NetConn_ReceivedMessage(netconn_t *conn, unsigned char *data, int len length -= 8; if (flags & NETFLAG_UNRELIABLE) { - if (sequence >= conn->unreliableReceiveSequence) + if (sequence >= conn->nq.unreliableReceiveSequence) { - if (sequence > conn->unreliableReceiveSequence) + if (sequence > conn->nq.unreliableReceiveSequence) { - count = sequence - conn->unreliableReceiveSequence; + count = sequence - conn->nq.unreliableReceiveSequence; droppedDatagrams += count; Con_DPrintf("Dropped %u datagram(s)\n", count); } - conn->unreliableReceiveSequence = sequence + 1; + conn->nq.unreliableReceiveSequence = sequence + 1; conn->lastMessageTime = realtime; conn->timeout = realtime + net_messagetimeout.value; unreliableMessagesReceived++; @@ -783,12 +902,12 @@ static int NetConn_ReceivedMessage(netconn_t *conn, unsigned char *data, int len } else if (flags & NETFLAG_ACK) { - if (sequence == (conn->sendSequence - 1)) + if (sequence == (conn->nq.sendSequence - 1)) { - if (sequence == conn->ackSequence) + if (sequence == conn->nq.ackSequence) { - conn->ackSequence++; - if (conn->ackSequence != conn->sendSequence) + conn->nq.ackSequence++; + if (conn->nq.ackSequence != conn->nq.sendSequence) Con_DPrint("ack sequencing error\n"); conn->lastMessageTime = realtime; conn->timeout = realtime + net_messagetimeout.value; @@ -817,10 +936,10 @@ static int NetConn_ReceivedMessage(netconn_t *conn, unsigned char *data, int len header = (unsigned int *)sendbuffer; header[0] = BigLong(packetLen | (NETFLAG_DATA | eom)); - header[1] = BigLong(conn->sendSequence); + header[1] = BigLong(conn->nq.sendSequence); memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); - conn->sendSequence++; + conn->nq.sendSequence++; if (NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress) == (int)packetLen) { @@ -844,11 +963,11 @@ static int NetConn_ReceivedMessage(netconn_t *conn, unsigned char *data, int len temppacket[0] = BigLong(8 | NETFLAG_ACK); temppacket[1] = BigLong(sequence); NetConn_Write(conn->mysocket, (unsigned char *)temppacket, 8, &conn->peeraddress); - if (sequence == conn->receiveSequence) + if (sequence == conn->nq.receiveSequence) { conn->lastMessageTime = realtime; conn->timeout = realtime + net_messagetimeout.value; - conn->receiveSequence++; + conn->nq.receiveSequence++; if( conn->receiveMessageLength + length <= (int)sizeof( conn->receiveMessage ) ) { memcpy(conn->receiveMessage + conn->receiveMessageLength, data, length); conn->receiveMessageLength += length; @@ -881,7 +1000,7 @@ static int NetConn_ReceivedMessage(netconn_t *conn, unsigned char *data, int len return 0; } -void NetConn_ConnectionEstablished(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress) +void NetConn_ConnectionEstablished(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, protocolversion_t initialprotocol) { cls.connect_trying = false; M_Update_Return_Reason(""); @@ -898,6 +1017,9 @@ void NetConn_ConnectionEstablished(lhnetsocket_t *mysocket, lhnetaddress_t *peer cls.demonum = -1; // not in the demo loop now cls.state = ca_connected; cls.signon = 0; // need all the signon messages before playing + cls.protocol = initialprotocol; + if (cls.protocol == PROTOCOL_QUAKEWORLD) + Cmd_ForwardStringToServer("new"); } int NetConn_IsLocalGame(void) @@ -939,6 +1061,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat if (length > 10 && !memcmp(string, "challenge ", 10) && cls.connect_trying) { + // darkplaces or quake3 char protocolnames[1400]; Protocol_Names(protocolnames, sizeof(protocolnames)); Con_Printf("\"%s\" received, sending connect request back to %s\n", string, addressstring2); @@ -946,10 +1069,27 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat NetConn_WriteString(mysocket, va("\377\377\377\377connect\\protocol\\darkplaces 3\\protocols\\%s\\challenge\\%s", protocolnames, string + 10), peeraddress); return true; } + if (length > 1 && string[0] == 'c' && string[1] >= '0' && string[1] <= '9') + { + // quakeworld + LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); + Con_Printf("\"%s\" received, sending QuakeWorld connect request back to %s\n", string, addressstring2); + M_Update_Return_Reason("Got QuakeWorld challenge response"); + cls.qport = qport.integer; + NetConn_WriteString(mysocket, va("\377\377\377\377connect 28 %i %i \"%s\"\n", cls.qport, atoi(string + 1), cls.userinfo), peeraddress); + } if (length == 6 && !memcmp(string, "accept", 6) && cls.connect_trying) { + // darkplaces or quake3 M_Update_Return_Reason("Accepted"); - NetConn_ConnectionEstablished(mysocket, peeraddress); + NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_DARKPLACES3); + return true; + } + if (length > 1 && string[0] == 'j' && cls.connect_trying) + { + // quakeworld + M_Update_Return_Reason("QuakeWorld Accepted"); + NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_QUAKEWORLD); return true; } if (length > 7 && !memcmp(string, "reject ", 7) && cls.connect_trying) @@ -1085,6 +1225,31 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat if (!strncmp(string, "ack", 3)) return true; */ + // QuakeWorld compatibility + if (length >= 1 && string[0] == 'j' && cls.connect_trying) + { + // accept message + M_Update_Return_Reason("Accepted"); + NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_QUAKEWORLD); + return true; + } + if (length > 1 && string[0] == 'c' && string[1] >= '0' && string[1] <= '9' && cls.connect_trying) + { + // challenge message + LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); + Con_Printf("challenge %s received, sending connect request back to %s\n", string + 1, addressstring2); + M_Update_Return_Reason("Got challenge response"); + cls.qport = qport.integer; + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2); + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "name", cl_name.string); + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "topcolor", va("%i", (cl_color.integer >> 4) & 15)); + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "bottomcolor", va("%i", (cl_color.integer) & 15)); + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "rate", va("%i", cl_rate.integer)); + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "msg", "1"); + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ver", engineversion); + NetConn_WriteString(mysocket, va("\377\377\377\377connect %i %i %i \"%s\"\n", 28, cls.qport, atoi(string + 1), cls.userinfo), peeraddress); + return true; + } if (string[0] == 'n') { // qw print command @@ -1094,6 +1259,13 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat // we're done processing this packet now return true; } + // quakeworld ingame packet + if (fromserver && cls.protocol == PROTOCOL_QUAKEWORLD && length >= 8 && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol)) == 2) + { + ret = 0; + CL_ParseServerMessage(); + return ret; + } // netquake control packets, supported for compatibility only if (length >= 5 && (control = BigLong(*((int *)data))) && (control & (~NETFLAG_LENGTH_MASK)) == (int)NETFLAG_CTL && (control & NETFLAG_LENGTH_MASK) == length) { @@ -1118,7 +1290,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat LHNETADDRESS_SetPort(&clientportaddress, port); } M_Update_Return_Reason("Accepted"); - NetConn_ConnectionEstablished(mysocket, &clientportaddress); + NetConn_ConnectionEstablished(mysocket, &clientportaddress, PROTOCOL_QUAKE); } break; case CCREP_REJECT: @@ -1181,7 +1353,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat return true; } ret = 0; - if (fromserver && length >= (int)NET_HEADERSIZE && (ret = NetConn_ReceivedMessage(cls.netcon, data, length)) == 2) + if (fromserver && length >= (int)NET_HEADERSIZE && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol)) == 2) CL_ParseServerMessage(); return ret; } @@ -1268,7 +1440,7 @@ void NetConn_ClientFrame(void) M_Update_Return_Reason("Connect: Failed"); return; } - // try challenge first (newer server) + // try challenge first (newer DP server or QW) NetConn_WriteString(cls.connect_mysocket, "\377\377\377\377getchallenge", &cls.connect_address); // then try netquake as a fallback (old server, or netquake) SZ_Clear(&net_message); @@ -1826,7 +1998,7 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat #endif if (host_client) { - if ((ret = NetConn_ReceivedMessage(host_client->netconnection, data, length)) == 2) + if ((ret = NetConn_ReceivedMessage(host_client->netconnection, data, length, sv.protocol)) == 2) { SV_VM_Begin(); SV_ReadClientMessage(); @@ -1953,7 +2125,10 @@ static void Net_Heartbeat_f(void) void PrintStats(netconn_t *conn) { - Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->sendSequence, conn->receiveSequence); + if ((cls.state == ca_connected && cls.protocol == PROTOCOL_QUAKEWORLD) || (sv.active && sv.protocol == PROTOCOL_QUAKEWORLD)) + Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->qw.outgoing_sequence, conn->qw.incoming_sequence); + else + Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->nq.sendSequence, conn->nq.receiveSequence); } void Net_Stats_f(void) diff --git a/netconn.h b/netconn.h index d29bc2e6..671dff1e 100755 --- a/netconn.h +++ b/netconn.h @@ -133,19 +133,58 @@ typedef struct netconn_s // reliable message that is currently sending // (for building fragments) - unsigned int ackSequence; - unsigned int sendSequence; - unsigned int unreliableSendSequence; int sendMessageLength; unsigned char sendMessage[NET_MAXMESSAGE]; // reliable message that is currently being received // (for putting together fragments) - unsigned int receiveSequence; - unsigned int unreliableReceiveSequence; int receiveMessageLength; unsigned char receiveMessage[NET_MAXMESSAGE]; + struct netconn_nq_s + { + unsigned int ackSequence; + unsigned int sendSequence; + unsigned int unreliableSendSequence; + + unsigned int receiveSequence; + unsigned int unreliableReceiveSequence; + } + nq; + struct netconn_qw_s + { + // QW protocol + qboolean fatal_error; + + float last_received; // for timeouts + + // the statistics are cleared at each client begin, because + // the server connecting process gives a bogus picture of the data + float frame_latency; // rolling average + float frame_rate; + + int drop_count; // dropped packets, cleared each level + int good_count; // cleared each level + + int qport; + + // bandwidth estimator + double cleartime; // if realtime > nc->cleartime, free to go + double rate; // seconds / byte + + // sequencing variables + int incoming_sequence; + int incoming_acknowledged; + int incoming_reliable_acknowledged; // single bit + + int incoming_reliable_sequence; // single bit, maintained local + + int outgoing_sequence; + int reliable_sequence; // single bit + int last_reliable_sequence; // sequence number of last send + } + qw; + char address[128]; } netconn_t; @@ -294,7 +333,7 @@ extern cvar_t sv_netport; extern cvar_t net_address; //extern cvar_t net_netaddress_ipv6; -int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data); +int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol); void NetConn_CloseClientPorts(void); void NetConn_OpenClientPorts(void); void NetConn_CloseServerPorts(void); diff --git a/protocol.c b/protocol.c index 970a4ef6..028bc6ff 100644 --- a/protocol.c +++ b/protocol.c @@ -56,6 +56,7 @@ protocolversioninfo[] = {15, "QUAKEDP"}, {250, "NEHAHRAMOVIE"}, {15, "QUAKE"}, + {28, "QUAKEWORLD"}, {0, NULL} }; diff --git a/protocol.h b/protocol.h index b1c8e290..20f655d8 100644 --- a/protocol.h +++ b/protocol.h @@ -759,5 +759,124 @@ void EntityFrame5_WriteFrame(sizebuf_t *msg, entityframe5_database_t *d, int num extern cvar_t developer_networkentities; +// QUAKEWORLD +// server to client +#define qw_svc_bad 0 +#define qw_svc_nop 1 +#define qw_svc_disconnect 2 +#define qw_svc_updatestat 3 // [byte] [byte] +#define qw_svc_setview 5 // [short] entity number +#define qw_svc_sound 6 // +#define qw_svc_print 8 // [byte] id [string] null terminated string +#define qw_svc_stufftext 9 // [string] stuffed into client's console buffer +#define qw_svc_setangle 10 // [angle3] set the view angle to this absolute value +#define qw_svc_serverdata 11 // [long] protocol ... +#define qw_svc_lightstyle 12 // [byte] [string] +#define qw_svc_updatefrags 14 // [byte] [short] +#define qw_svc_stopsound 16 // +#define qw_svc_damage 19 +#define qw_svc_spawnstatic 20 +#define qw_svc_spawnbaseline 22 +#define qw_svc_temp_entity 23 // variable +#define qw_svc_setpause 24 // [byte] on / off +#define qw_svc_centerprint 26 // [string] to put in center of the screen +#define qw_svc_killedmonster 27 +#define qw_svc_foundsecret 28 +#define qw_svc_spawnstaticsound 29 // [coord3] [byte] samp [byte] vol [byte] aten +#define qw_svc_intermission 30 // [vec3_t] origin [vec3_t] angle +#define qw_svc_finale 31 // [string] text +#define qw_svc_cdtrack 32 // [byte] track +#define qw_svc_sellscreen 33 +#define qw_svc_smallkick 34 // set client punchangle to 2 +#define qw_svc_bigkick 35 // set client punchangle to 4 +#define qw_svc_updateping 36 // [byte] [short] +#define qw_svc_updateentertime 37 // [byte] [float] +#define qw_svc_updatestatlong 38 // [byte] [long] +#define qw_svc_muzzleflash 39 // [short] entity +#define qw_svc_updateuserinfo 40 // [byte] slot [long] uid +#define qw_svc_download 41 // [short] size [size bytes] +#define qw_svc_playerinfo 42 // variable +#define qw_svc_nails 43 // [byte] num [48 bits] xyzpy 12 12 12 4 8 +#define qw_svc_chokecount 44 // [byte] packets choked +#define qw_svc_modellist 45 // [strings] +#define qw_svc_soundlist 46 // [strings] +#define qw_svc_packetentities 47 // [...] +#define qw_svc_deltapacketentities 48 // [...] +#define qw_svc_maxspeed 49 // maxspeed change, for prediction +#define qw_svc_entgravity 50 // gravity change, for prediction +#define qw_svc_setinfo 51 // setinfo on a client +#define qw_svc_serverinfo 52 // serverinfo +#define qw_svc_updatepl 53 // [byte] [byte] +// QUAKEWORLD +// client to server +#define qw_clc_bad 0 +#define qw_clc_nop 1 +#define qw_clc_move 3 // [[usercmd_t] +#define qw_clc_stringcmd 4 // [string] message +#define qw_clc_delta 5 // [byte] sequence number, requests delta compression of message +#define qw_clc_tmove 6 // teleport request, spectator only +#define qw_clc_upload 7 // teleport request, spectator only +// QUAKEWORLD +// playerinfo flags from server +// playerinfo allways sends: playernum, flags, origin[] and framenumber +#define PF_MSEC (1<<0) +#define PF_COMMAND (1<<1) +#define PF_VELOCITY1 (1<<2) +#define PF_VELOCITY2 (1<<3) +#define PF_VELOCITY3 (1<<4) +#define PF_MODEL (1<<5) +#define PF_SKINNUM (1<<6) +#define PF_EFFECTS (1<<7) +#define PF_WEAPONFRAME (1<<8) // only sent for view player +#define PF_DEAD (1<<9) // don't block movement any more +#define PF_GIB (1<<10) // offset the view height differently +#define PF_NOGRAV (1<<11) // don't apply gravity for prediction +// QUAKEWORLD +// if the high bit of the client to server byte is set, the low bits are +// client move cmd bits +// ms and angle2 are allways sent, the others are optional +#define QW_CM_ANGLE1 (1<<0) +#define QW_CM_ANGLE3 (1<<1) +#define QW_CM_FORWARD (1<<2) +#define QW_CM_SIDE (1<<3) +#define QW_CM_UP (1<<4) +#define QW_CM_BUTTONS (1<<5) +#define QW_CM_IMPULSE (1<<6) +#define QW_CM_ANGLE2 (1<<7) +// QUAKEWORLD +// the first 16 bits of a packetentities update holds 9 bits +// of entity number and 7 bits of flags +#define QW_U_ORIGIN1 (1<<9) +#define QW_U_ORIGIN2 (1<<10) +#define QW_U_ORIGIN3 (1<<11) +#define QW_U_ANGLE2 (1<<12) +#define QW_U_FRAME (1<<13) +#define QW_U_REMOVE (1<<14) // REMOVE this entity, don't add it +#define QW_U_MOREBITS (1<<15) +// if MOREBITS is set, these additional flags are read in next +#define QW_U_ANGLE1 (1<<0) +#define QW_U_ANGLE3 (1<<1) +#define QW_U_MODEL (1<<2) +#define QW_U_COLORMAP (1<<3) +#define QW_U_SKIN (1<<4) +#define QW_U_EFFECTS (1<<5) +#define QW_U_SOLID (1<<6) // the entity should be solid for prediction +// QUAKEWORLD +// temp entity events +#define QW_TE_SPIKE 0 +#define QW_TE_SUPERSPIKE 1 +#define QW_TE_GUNSHOT 2 +#define QW_TE_EXPLOSION 3 +#define QW_TE_TAREXPLOSION 4 +#define QW_TE_LIGHTNING1 5 +#define QW_TE_LIGHTNING2 6 +#define QW_TE_WIZSPIKE 7 +#define QW_TE_KNIGHTSPIKE 8 +#define QW_TE_LIGHTNING3 9 +#define QW_TE_LAVASPLASH 10 +#define QW_TE_TELEPORT 11 +#define QW_TE_BLOOD 12 +#define QW_TE_LIGHTNINGBLOOD 13 + #endif diff --git a/quakedef.h b/quakedef.h index fcba8db0..3c0cb6a5 100644 --- a/quakedef.h +++ b/quakedef.h @@ -183,6 +183,10 @@ extern char engineversion[128]; #define MAX_SCOREBOARD 64 // LordHavoc: increased name limit from 32 to 64 characters #define MAX_SCOREBOARDNAME 64 +// infostring sizes used by QuakeWorld support +#define MAX_USERINFO_STRING 196 +#define MAX_SERVERINFO_STRING 512 +#define MAX_LOCALINFO_STRING 32768 #include "zone.h" #include "fs.h" @@ -222,6 +226,7 @@ extern char engineversion[128]; extern qboolean noclip_anglehack; +extern char engineversion[128]; extern cvar_t developer; extern double host_frametime; diff --git a/server.h b/server.h index 0e9e3938..aab73532 100644 --- a/server.h +++ b/server.h @@ -32,6 +32,8 @@ typedef struct server_static_s int serverflags; // cleared when at SV_SpawnServer qboolean changelevel_issued; + // server infostring + char serverinfo[MAX_SERVERINFO_STRING]; } server_static_t; //============================================================================= diff --git a/sv_main.c b/sv_main.c index 84c5e042..f1bf0bc1 100644 --- a/sv_main.c +++ b/sv_main.c @@ -1246,7 +1246,7 @@ void SV_SendClientDatagram (client_t *client) } // send the datagram - NetConn_SendUnreliableMessage (client->netconnection, &msg); + NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol); } /* @@ -1349,6 +1349,9 @@ void SV_SendClientMessages (void) { int i, prepared = false; + if (sv.protocol == PROTOCOL_QUAKEWORLD) + Sys_Error("SV_SendClientMessages: no quakeworld support\n"); + // update frags, names, etc SV_UpdateToReliableMessages(); -- 2.39.2