コード例 #1
0
ファイル: cl_game.c プロジェクト: chrisglass/ufoai
/**
 * @brief Called during startup of mission to send team info
 */
void GAME_SpawnSoldiers (void)
{
	const cgame_export_t *list = GAME_GetCurrentType();
	qboolean spawnStatus;

	/* this callback is responsible to set up the cl.chrList */
	if (list && list->Spawn)
		spawnStatus = list->Spawn();
	else
		spawnStatus = GAME_Spawn();

	if (spawnStatus && cl.chrList.num > 0) {
		struct dbuffer *msg;

		/* send team info */
		msg = new_dbuffer();
		GAME_SendCurrentTeamSpawningInfo(msg, &cl.chrList);
		NET_WriteMsg(cls.netStream, msg);

		msg = new_dbuffer();
		NET_WriteByte(msg, clc_stringcmd);
		NET_WriteString(msg, "spawn\n");
		NET_WriteMsg(cls.netStream, msg);

		GAME_InitializeBattlescape(&cl.chrList);
	}
}
コード例 #2
0
/**
 * @brief Called when the player is totally leaving the server, either willingly
 * or unwillingly. This is NOT called if the entire server is quitting
 * or crashing.
 */
void SV_DropClient (client_t * drop, const char *message)
{
	/* add the disconnect */
	dbuffer msg(2 + strlen(message));
	NET_WriteByte(&msg, svc_disconnect);
	NET_WriteString(&msg, message);
	NET_WriteMsg(drop->stream, msg);
	SV_BroadcastPrintf(PRINT_CHAT, "%s was dropped from the server - reason: %s\n", drop->name, message);

	if (drop->state == cs_spawned || drop->state == cs_spawning) {
		/* call the prog function for removing a client */
		/* this will remove the body, among other things */
		const ScopedMutex scopedMutex(svs.serverMutex);
		svs.ge->ClientDisconnect(drop->player);
	}

	NET_StreamFinished(drop->stream);
	drop->stream = NULL;

	drop->player->inuse = false;
	SV_SetClientState(drop, cs_free);
	drop->name[0] = 0;

	if (svs.abandon) {
		int count = 0;
		client_t *cl = NULL;
		while ((cl = SV_GetNextClient(cl)) != NULL)
			if (cl->state >= cs_connected)
				count++;
		if (count == 0)
			svs.killserver = true;
	}
}
コード例 #3
0
ファイル: cl_main.cpp プロジェクト: Astrocoderguy/ufoai
/**
 * @brief
 * @note Called after precache was sent from the server
 * @sa SV_Configstrings_f
 * @sa CL_Precache_f
 */
void CL_RequestNextDownload (void)
{
	if (cls.state != ca_connected) {
		Com_Printf("CL_RequestNextDownload: Not connected (%i)\n", cls.state);
		return;
	}

	/* Use the map data from the server */
	cl.mapTiles = SV_GetMapTiles();
	cl.mapData = SV_GetMapData();

	/* as a multiplayer client we have to load the map here and
	 * check the compatibility with the server */
	if (!Com_ServerState() && !CL_CanMultiplayerStart())
		return;

	CL_ViewLoadMedia();

	dbuffer msg(7);
	/* send begin */
	/* this will activate the render process (see client state ca_active) */
	NET_WriteByte(&msg, clc_stringcmd);
	/* see CL_StartGame */
	NET_WriteString(&msg, NET_STATE_BEGIN "\n");
	NET_WriteMsg(cls.netStream, msg);

	cls.waitingForStart = CL_Milliseconds();

	S_MumbleLink();
}
コード例 #4
0
/**
 * @brief Responds with teaminfo such as free team num
 * @sa CL_ParseTeamInfoMessage
 */
static void SVC_TeamInfo (struct net_stream *s)
{
	client_t *cl;
	dbuffer msg;
	char infoGlobal[MAX_INFO_STRING] = "";

	NET_WriteByte(&msg, clc_oob);
	NET_WriteRawString(&msg, "teaminfo\n");

	Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_teamplay", Cvar_GetString("sv_teamplay"));
	Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxteams", Cvar_GetString("sv_maxteams"));
	Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxplayersperteam", Cvar_GetString("sv_maxplayersperteam"));
	NET_WriteString(&msg, infoGlobal);

	cl = NULL;
	while ((cl = SV_GetNextClient(cl)) != NULL) {
		if (cl->state >= cs_connected) {
			char infoPlayer[MAX_INFO_STRING] = "";
			/* show players that already have a team with their teamnum */
			int teamId = svs.ge->ClientGetTeamNum(cl->player);
			if (!teamId)
				teamId = TEAM_NO_ACTIVE;
			Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_team", teamId);
			Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_ready", svs.ge->ClientIsReady(cl->player));
			Info_SetValueForKey(infoPlayer, sizeof(infoPlayer), "cl_name", cl->name);
			NET_WriteString(&msg, infoPlayer);
		}
	}

	NET_WriteByte(&msg, 0);

	NET_WriteMsg(s, msg);
}
コード例 #5
0
ファイル: cl_main.cpp プロジェクト: Astrocoderguy/ufoai
/**
 * @brief Sets the @c cls.state to @c ca_disconnected and informs the server
 * @sa CL_Drop
 * @note Goes from a connected state to disconnected state
 * Sends a disconnect message to the server
 * This is also called on @c Com_Error, so it shouldn't cause any errors
 */
void CL_Disconnect (void)
{
	if (cls.state < ca_connecting)
		return;

	Com_Printf("Disconnecting...\n");

	/* send a disconnect message to the server */
	if (!Com_ServerState()) {
		dbuffer msg;
		NET_WriteByte(&msg, clc_stringcmd);
		NET_WriteString(&msg, NET_STATE_DISCONNECT "\n");
		NET_WriteMsg(cls.netStream, msg);
		/* make sure, that this is send */
		NET_Wait(0);
	}

	NET_StreamFinished(cls.netStream);
	cls.netStream = nullptr;

	CL_ClearState();

	S_Stop();

	R_ShutdownModels(false);
	R_FreeWorldImages();

	CL_SetClientState(ca_disconnected);
	CL_ClearBattlescapeEvents();
	GAME_EndBattlescape();
}
コード例 #6
0
ファイル: sv_main.cpp プロジェクト: Ed-von-Schleck/ufoai
/**
 * @brief Responds with all the info that the server browser can see
 * @sa SV_StatusString
 */
static void SVC_Status (struct net_stream* s)
{
    if (SVC_RateLimitAddress(*s)) {
        Com_DPrintf(DEBUG_SERVER, "SVC_Status: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s));
        return;
    }

    /* Allow getstatus to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */
    if (SVC_RateLimit(&outboundLeakyBucket, 10, 100)) {
        Com_DPrintf(DEBUG_SERVER, "SVC_Status: rate limit exceeded, dropping request\n");
        return;
    }

    dbuffer msg;
    NET_WriteByte(&msg, svc_oob);
    NET_WriteRawString(&msg, SV_CMD_PRINT "\n");
    char info[MAX_INFO_STRING];
    NET_WriteRawString(&msg, Cvar_Serverinfo(info, sizeof(info)));
    NET_WriteRawString(&msg, "\n");

    client_t* cl = nullptr;
    while ((cl = SV_GetNextClient(cl)) != nullptr) {
        if (cl->state <= cs_free)
            continue;

        char player[1024];
        Com_sprintf(player, sizeof(player), "%i \"%s\"\n", svs.ge->ClientGetTeamNum(*cl->player), cl->name);
        NET_WriteRawString(&msg, player);
    }

    NET_WriteMsg(s, msg);
}
コード例 #7
0
ファイル: sv_send.c プロジェクト: chrisglass/ufoai
/**
 * @sa SV_BroadcastCommand
 */
void SV_ClientCommand (client_t *client, const char *fmt, ...)
{
	va_list ap;
	char str[1024];
	struct dbuffer *msg = new_dbuffer();

	NET_WriteByte(msg, svc_stufftext);

	va_start(ap, fmt);
	NET_VPrintf(msg, fmt, ap, str, sizeof(str));
	va_end(ap);

	NET_WriteMsg(client->stream, msg);
}
コード例 #8
0
ファイル: sv_send.cpp プロジェクト: ibrahimmusba/ufoai
/**
 * @sa SV_BroadcastCommand
 */
void SV_ClientCommand (client_t *client, const char *fmt, ...)
{
	va_list ap;
	char str[MAX_SVC_STUFFTEXT];
	dbuffer msg;

	NET_WriteByte(&msg, svc_stufftext);

	va_start(ap, fmt);
	NET_VPrintf(&msg, fmt, ap, str, sizeof(str));
	va_end(ap);

	NET_WriteMsg(client->stream, msg);
}
コード例 #9
0
ファイル: cl_main.cpp プロジェクト: Astrocoderguy/ufoai
/**
 * @brief Send the userinfo to the server (and to all other clients)
 * when they changed (CVAR_USERINFO)
 * @sa CL_Connect
 */
static void CL_SendChangedUserinfos (void)
{
	/* send a userinfo update if needed */
	if (cls.state < ca_connected)
		return;
	if (!Com_IsUserinfoModified())
		return;
	char info[MAX_INFO_STRING];
	const char* userInfo = Cvar_Userinfo(info, sizeof(info));
	dbuffer msg(strlen(userInfo) + 2);
	NET_WriteByte(&msg, clc_userinfo);
	NET_WriteString(&msg, userInfo);
	NET_WriteMsg(cls.netStream, msg);
	Com_SetUserinfoModified(false);
}
コード例 #10
0
ファイル: cl_main.cpp プロジェクト: Astrocoderguy/ufoai
static void CL_ForwardToServer_f (void)
{
	if (cls.state != ca_connected && cls.state != ca_active) {
		Com_Printf("Can't \"%s\", not connected\n", Cmd_Argv(0));
		return;
	}

	/* don't forward the first argument */
	if (Cmd_Argc() > 1) {
		const int len = strlen(Cmd_Args()) + 1;
		dbuffer msg(len + 1);
		NET_WriteByte(&msg, clc_stringcmd);
		msg.add(Cmd_Args(), len);
		NET_WriteMsg(cls.netStream, msg);
	}
}
コード例 #11
0
ファイル: e_server.cpp プロジェクト: AresAndy/ufoai
/**
 * @brief Finishes the current turn of the player in battlescape and starts the turn for the next team.
 */
static void CL_NextRound_f (void)
{
	/* can't end round if we are not in battlescape */
	if (!CL_BattlescapeRunning())
		return;

	/* can't end round if we're not active */
	if (!cls.isOurRound()) {
		HUD_DisplayMessage(_("It is not your turn!"));
		return;
	}

	/* send endround */
	dbuffer msg;
	NET_WriteByte(&msg, clc_endround);
	NET_WriteMsg(cls.netStream, msg);
}
コード例 #12
0
ファイル: sv_send.cpp プロジェクト: ibrahimmusba/ufoai
/**
 * @brief Sends text across to be displayed if the level passes
 */
void SV_ClientPrintf (client_t *cl, int level, const char *fmt, ...)
{
	if (level > cl->messagelevel)
		return;

	dbuffer msg;
	NET_WriteByte(&msg, svc_print);
	NET_WriteByte(&msg, level);

	va_list argptr;
	va_start(argptr, fmt);
	char str[MAX_SVC_PRINT];
	NET_VPrintf(&msg, fmt, argptr, str, sizeof(str));
	va_end(argptr);

	NET_WriteMsg(cl->stream, msg);
}
コード例 #13
0
ファイル: e_server.c プロジェクト: chrisglass/ufoai
/**
 * @brief Finishes the current round of the player in battlescape and starts the round for the next team.
 */
static void CL_NextRound_f (void)
{
	struct dbuffer *msg;
	/* can't end round if we are not in battlescape */
	if (!CL_BattlescapeRunning())
		return;

	/* can't end round if we're not active */
	if (cls.team != cl.actTeam) {
		HUD_DisplayMessage(_("This isn't your round"));
		return;
	}

	/* send endround */
	msg = new_dbuffer();
	NET_WriteByte(msg, clc_endround);
	NET_WriteMsg(cls.netStream, msg);
}
コード例 #14
0
static void SV_PingPlayers (void)
{
	client_t *cl;
	/* check for time wraparound */
	if (svs.lastPing > svs.realtime)
		svs.lastPing = svs.realtime;

	if (svs.realtime - svs.lastPing < PING_SECONDS * 1000)
		return;					/* not time to send yet */

	svs.lastPing = svs.realtime;
	cl = NULL;
	while ((cl = SV_GetNextClient(cl)) != NULL)
		if (cl->state != cs_free) {
			dbuffer msg(1);
			NET_WriteByte(&msg, svc_ping);
			NET_WriteMsg(cl->stream, msg);
		}
}
コード例 #15
0
ファイル: cl_main.cpp プロジェクト: Astrocoderguy/ufoai
/**
 * @brief adds the current command line as a clc_stringcmd to the client message.
 * things like action, turn, etc, are commands directed to the server,
 * so when they are typed in at the console, they will need to be forwarded.
 */
void Cmd_ForwardToServer (void)
{
	const char* cmd = Cmd_Argv(0);

	if (cls.state <= ca_connected || cmd[0] == '-' || cmd[0] == '+') {
		Com_Printf("Unknown command \"%s\" - wasn't sent to server\n", cmd);
		return;
	}

	dbuffer msg;
	NET_WriteByte(&msg, clc_stringcmd);
	msg.add(cmd, strlen(cmd));
	if (Cmd_Argc() > 1) {
		msg.add(" ", 1);
		msg.add(Cmd_Args(), strlen(Cmd_Args()));
	}
	msg.add("", 1);
	NET_WriteMsg(cls.netStream, msg);
}
コード例 #16
0
ファイル: sv_send.c プロジェクト: chrisglass/ufoai
/**
 * @brief Sends text across to be displayed if the level passes
 */
void SV_ClientPrintf (client_t * cl, int level, const char *fmt, ...)
{
	va_list argptr;
	struct dbuffer *msg;
	char str[1024];

	if (level > cl->messagelevel)
		return;

	msg = new_dbuffer();
	NET_WriteByte(msg, svc_print);
	NET_WriteByte(msg, level);

	va_start(argptr, fmt);
	NET_VPrintf(msg, fmt, argptr, str, sizeof(str));
	va_end(argptr);

	NET_WriteMsg(cl->stream, msg);
}
コード例 #17
0
ファイル: sv_main.cpp プロジェクト: Ed-von-Schleck/ufoai
/**
 * @brief Responds with teaminfo such as free team num
 * @sa CL_ParseTeamInfoMessage
 */
static void SVC_TeamInfo (struct net_stream* s)
{
    if (SVC_RateLimitAddress(*s)) {
        Com_DPrintf(DEBUG_SERVER, "SVC_TeamInfo: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s));
        return;
    }

    /* Allow getinfo to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */
    if (SVC_RateLimit(&outboundLeakyBucket)) {
        Com_DPrintf(DEBUG_SERVER, "SVC_TeamInfo: rate limit exceeded, dropping request\n");
        return;
    }

    char infoGlobal[MAX_INFO_STRING] = "";
    Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_teamplay", Cvar_GetString("sv_teamplay"));
    Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxteams", Cvar_GetString("sv_maxteams"));
    Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxplayersperteam", Cvar_GetString("sv_maxplayersperteam"));

    dbuffer msg;
    NET_WriteByte(&msg, svc_oob);
    NET_WriteRawString(&msg, "teaminfo\n");
    NET_WriteString(&msg, infoGlobal);

    client_t* cl = nullptr;
    while ((cl = SV_GetNextClient(cl)) != nullptr) {
        if (cl->state < cs_connected)
            continue;
        char infoPlayer[MAX_INFO_STRING] = "";
        /* show players that already have a team with their teamnum */
        int teamId = svs.ge->ClientGetTeamNum(*cl->player);
        if (!teamId)
            teamId = TEAM_NO_ACTIVE;
        Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_team", teamId);
        Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_ready", svs.ge->ClientIsReady(cl->player));
        Info_SetValueForKey(infoPlayer, sizeof(infoPlayer), "cl_name", cl->name);
        NET_WriteString(&msg, infoPlayer);
    }

    NET_WriteByte(&msg, 0);

    NET_WriteMsg(s, msg);
}
コード例 #18
0
/**
 * @brief Responds with all the info that the server browser can see
 * @sa SV_StatusString
 */
static void SVC_Status (struct net_stream *s)
{
	client_t *cl;
	char player[1024];
	dbuffer msg;
	NET_WriteByte(&msg, clc_oob);
	NET_WriteRawString(&msg, "print\n");

	NET_WriteRawString(&msg, Cvar_Serverinfo());
	NET_WriteRawString(&msg, "\n");

	cl = NULL;
	while ((cl = SV_GetNextClient(cl)) != NULL) {
		if (cl->state > cs_free) {
			Com_sprintf(player, sizeof(player), "%i \"%s\"\n", svs.ge->ClientGetTeamNum(cl->player), cl->name);
			NET_WriteRawString(&msg, player);
		}
	}

	NET_WriteMsg(s, msg);
}
コード例 #19
0
ファイル: sv_user.c プロジェクト: kevlund/ufoai
/**
 * @brief Sends the first message from the server to a connected client.
 * This will be sent on the initial connection and upon each server load.
 * Client reads via CL_ParseServerData in cl_parse.c
 * @sa CL_Reconnect_f
 * @sa CL_ConnectionlessPacket
 */
static void SV_New_f (client_t *cl)
{
	Com_DPrintf(DEBUG_SERVER, "New() from %s\n", cl->name);

	if (cl->state != cs_connected) {
		if (cl->state == cs_spawning) {
			/* client typed 'reconnect/new' while connecting. */
			Com_Printf("SV_New_f: client typed 'reconnect/new' while connecting\n");
			SV_ClientCommand(cl, "\ndisconnect\nreconnect\n");
			SV_DropClient(cl, "");
		} else
			Com_DPrintf(DEBUG_SERVER, "WARNING: Illegal 'new' from %s, client state %d. This shouldn't happen...\n", cl->name, cl->state);
		return;
	}

	/* client state to prevent multiple new from causing high cpu / overflows. */
	SV_SetClientState(cl, cs_spawning);

	/* serverdata needs to go over for all types of servers
	 * to make sure the protocol is right, and to set the gamedir */

	/* send the serverdata */
	{
		const int playernum = cl - SV_GetClient(0);
		struct dbuffer *msg = new_dbuffer();
		NET_WriteByte(msg, svc_serverdata);
		NET_WriteLong(msg, PROTOCOL_VERSION);

		NET_WriteShort(msg, playernum);

		/* send full levelname */
		NET_WriteString(msg, SV_GetConfigString(CS_NAME));

		NET_WriteMsg(cl->stream, msg);
	}

	/* game server */
	if (Com_ServerState() == ss_game) {
		int i;
		for (i = 0; i < MAX_CONFIGSTRINGS; i++) {
			const char *configString;
			/* CS_TILES and CS_POSITIONS can stretch over multiple configstrings,
			 * so don't send the middle parts again. */
			if (i > CS_TILES && i < CS_POSITIONS)
				continue;
			if (i > CS_POSITIONS && i < CS_MODELS)
				continue;

			configString = SV_GetConfigString(i);
			if (configString[0] != '\0') {
				struct dbuffer *msg = new_dbuffer();
				Com_DPrintf(DEBUG_SERVER, "sending configstring %d: %s\n", i, configString);
				NET_WriteByte(msg, svc_configstring);
				NET_WriteShort(msg, i);
				NET_WriteString(msg, configString);
				/* enqueue and free msg */
				NET_WriteMsg(cl->stream, msg);
			}
		}
	}

	SV_ClientCommand(cl, "precache\n");
}
コード例 #20
0
ファイル: cl_main.cpp プロジェクト: Astrocoderguy/ufoai
/**
 * @brief Responses to broadcasts, etc
 * @sa CL_ReadPackets
 * @sa CL_Frame
 * @sa SVC_DirectConnect
 * @param[in,out] msg The client stream message buffer to read from
 */
static void CL_ConnectionlessPacket (dbuffer* msg)
{
	char s[512];
	NET_ReadStringLine(msg, s, sizeof(s));

	Cmd_TokenizeString(s, false);

	const char* c = Cmd_Argv(0);
	Com_DPrintf(DEBUG_CLIENT, "server OOB: %s (%s)\n", c, Cmd_Args());

	/* server connection */
	if (Q_streq(c, CL_CMD_CLIENT_CONNECT)) {
		int i;
		for (i = 1; i < Cmd_Argc(); i++) {
			if (char const* const p = Q_strstart(Cmd_Argv(i), "dlserver=")) {
				Com_sprintf(cls.downloadReferer, sizeof(cls.downloadReferer), "ufo://%s", cls.servername);
				CL_SetHTTPServer(p);
				if (cls.downloadServer[0])
					Com_Printf("HTTP downloading enabled, URL: %s\n", cls.downloadServer);
			}
		}
		if (cls.state == ca_connected) {
			Com_Printf("Dup connect received. Ignored.\n");
			return;
		}
		dbuffer buf(5);
		NET_WriteByte(&buf, clc_stringcmd);
		NET_WriteString(&buf, NET_STATE_NEW "\n");
		NET_WriteMsg(cls.netStream, buf);
		GAME_InitMissionBriefing(_("Loading"));
		return;
	}

	/* remote command from gui front end */
	if (Q_streq(c, CL_CMD_COMMAND)) {
		if (!NET_StreamIsLoopback(cls.netStream)) {
			Com_Printf("Command packet from remote host. Ignored.\n");
			return;
		} else {
			char str[512];
			NET_ReadString(msg, str, sizeof(str));
			Cbuf_AddText("%s\n", str);
		}
		return;
	}

	/* ping from server */
	if (Q_streq(c, CL_CMD_PING)) {
		NET_OOB_Printf(cls.netStream, SV_CMD_ACK);
		return;
	}

	/* echo request from server */
	if (Q_streq(c, CL_CMD_ECHO)) {
		NET_OOB_Printf(cls.netStream, "%s", Cmd_Argv(1));
		return;
	}

	/* print */
	if (Q_streq(c, SV_CMD_PRINT)) {
		NET_ReadString(msg, popupText, sizeof(popupText));
		/* special reject messages needs proper handling */
		if (strstr(popupText, REJ_PASSWORD_REQUIRED_OR_INCORRECT)) {
			UI_PushWindow("serverpassword");
			if (Q_strvalid(Cvar_GetString("password"))) {
				Cvar_Set("password", "");
				CL_Drop();
				UI_Popup(_("Connection failure"), _("The password you specified was wrong."));
			} else {
				CL_Drop();
				UI_Popup(_("Connection failure"), _("This server requires a password."));
			}
		} else if (strstr(popupText, REJ_SERVER_FULL)) {
			CL_Drop();
			UI_Popup(_("Connection failure"), _("This server is full."));
		} else if (strstr(popupText, REJ_BANNED)) {
			CL_Drop();
			UI_Popup(_("Connection failure"), _("You are banned on this server."));
		} else if (strstr(popupText, REJ_GAME_ALREADY_STARTED)) {
			CL_Drop();
			UI_Popup(_("Connection failure"), _("The game has already started."));
		} else if (strstr(popupText, REJ_SERVER_VERSION_MISMATCH)) {
			CL_Drop();
			UI_Popup(_("Connection failure"), _("The server is running a different version of the game."));
		} else if (strstr(popupText, BAD_RCON_PASSWORD)) {
			Cvar_Set("rcon_password", "");
			UI_Popup(_("Bad rcon password"), _("The rcon password you specified was wrong."));
		} else if (strstr(popupText, REJ_CONNECTION_REFUSED)) {
			CL_Drop();
			UI_Popup(_("Connection failure"), _("The server refused the connection."));
		} else if (Q_strvalid(popupText)) {
			UI_Popup(_("Notice"), _(popupText));
		}
		return;
	}

	if (!GAME_HandleServerCommand(c, msg))
		Com_Printf("Unknown command received \"%s\"\n", c);
}