}
// 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)
{
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"};
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;
}
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();
"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"};
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
msg.data = buf;
msg.maxsize = sizeof(buf);
MSG_WriteChar(&msg, svc_nop);
- NetConn_SendUnreliableMessage(cls.netcon, &msg);
+ NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol);
}
}
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
{
cactive_t state;
+ // value of "qport" cvar at time of connection
+ int qport;
+
// demo loop control
// -1 = don't play demos
int demonum;
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;
// [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
void CL_Parse_Shutdown(void);
void CL_ParseServerMessage(void);
void CL_Parse_DumpPacket(void);
+extern cvar_t qport;
//
// view
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;
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
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)
#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)
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);
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;
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;
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;
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");
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;
}
}
+/*
+====================
+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
*/
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)
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);
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)
}
}
-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;
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++;
}
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;
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)
{
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;
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("");
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)
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);
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)
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
// 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)
{
LHNETADDRESS_SetPort(&clientportaddress, port);
}
M_Update_Return_Reason("Accepted");
- NetConn_ConnectionEstablished(mysocket, &clientportaddress);
+ NetConn_ConnectionEstablished(mysocket, &clientportaddress, PROTOCOL_QUAKE);
}
break;
case CCREP_REJECT:
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;
}
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);
#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();
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)
// 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;
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);
{15, "QUAKEDP"},
{250, "NEHAHRAMOVIE"},
{15, "QUAKE"},
+ {28, "QUAKEWORLD"},
{0, NULL}
};
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
#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"
extern qboolean noclip_anglehack;
+extern char engineversion[128];
extern cvar_t developer;
extern double host_frametime;
int serverflags;
// cleared when at SV_SpawnServer
qboolean changelevel_issued;
+ // server infostring
+ char serverinfo[MAX_SERVERINFO_STRING];
} server_static_t;
//=============================================================================
}
// send the datagram
- NetConn_SendUnreliableMessage (client->netconnection, &msg);
+ NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol);
}
/*
{
int i, prepared = false;
+ if (sv.protocol == PROTOCOL_QUAKEWORLD)
+ Sys_Error("SV_SendClientMessages: no quakeworld support\n");
+
// update frags, names, etc
SV_UpdateToReliableMessages();