beginnings of qw protocol support
authorhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Fri, 24 Feb 2006 06:04:40 +0000 (06:04 +0000)
committerhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Fri, 24 Feb 2006 06:04:40 +0000 (06:04 +0000)
git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@6026 d7cf8633-e32d-0410-b094-e92efae38249

16 files changed:
cl_input.c
cl_main.c
cl_parse.c
client.h
common.h
cvar.c
cvar.h
host.c
host_cmd.c
netconn.c
netconn.h
protocol.c
protocol.h
quakedef.h
server.h
sv_main.c

index c38a8bf..c6eb720 100644 (file)
@@ -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)
        {
index d008691..8386938 100644 (file)
--- 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();
index 44d20b2..43307fb 100644 (file)
@@ -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    // <see code>
+       "",                                                             // 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   // <see code>
+       "",                                                             // 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);
        }
 }
 
index 9dbe068..4ae120e 100644 (file)
--- 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
index b1cba03..3bba8dd 100644 (file)
--- 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 990ebdb..f3b9e70 100644 (file)
--- 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 fbf3b96..d2b5715 100644 (file)
--- 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 6edefc9..34d1417 100644 (file)
--- 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);
index 98b7d09..865ecb0 100644 (file)
@@ -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 <name or userid>
+
+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 <username / userid>\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 <complete info string>\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 <complete info string>\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 [ <key> <value> ]\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);
index c729f2b..0ce9c74 100755 (executable)
--- 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)
index d29bc2e..671dff1 100755 (executable)
--- 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);
index 970a4ef..028bc6f 100644 (file)
@@ -56,6 +56,7 @@ protocolversioninfo[] =
        {15, "QUAKEDP"},
        {250, "NEHAHRAMOVIE"},
        {15, "QUAKE"},
+       {28, "QUAKEWORLD"},
        {0, NULL}
 };
 
index b1c8e29..20f655d 100644 (file)
@@ -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       // <see code>
+#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      // <see code>
+#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
 
index fcba8db..3c0cb6a 100644 (file)
@@ -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;
index 0e9e393..aab7353 100644 (file)
--- 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;
 
 //=============================================================================
index 84c5e04..f1bf0bc 100644 (file)
--- 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();