示例#1
0
static void SV_CheckStartMatch (void)
{
	client_t *cl;

	if (!sv->spawned || sv->started)
		return;

	if (sv_maxclients->integer > 1) {
		cl = NULL;
		while ((cl = SV_GetNextClient(cl)) != NULL) {
			/* all players must have their actors spawned */
			if (cl->state != cs_spawned)
				return;
		}
	} else if (SV_GetClient(0)->state != cs_spawned) {
		/* in single player mode we must have received the 'spawnsoldiers' */
		return;
	}

	sv->started = true;

	cl = NULL;
	while ((cl = SV_GetNextClient(cl)) != NULL)
		if (cl->state != cs_free)
			SV_ClientCommand(cl, "startmatch\n");
}
示例#2
0
/**
 * @brief If all connected clients have set their ready flag the server will spawn the clients
 * and that change the client state.
 * @sa SV_Spawn_f
 */
static void SV_CheckSpawnSoldiers (void)
{
	client_t *cl;

	/* already started? */
	if (sv->spawned)
		return;

	if (sv_maxclients->integer > 1) {
		cl = NULL;
		while ((cl = SV_GetNextClient(cl)) != NULL) {
			/* all players must be connected and all of them must have set
			 * the ready flag */
			if (cl->state != cs_began || !cl->player->isReady)
				return;
		}
	} else if (SV_GetClient(0)->state != cs_began) {
		/* in single player mode we must have received the 'begin' */
		return;
	}

	sv->spawned = qtrue;

	cl = NULL;
	while ((cl = SV_GetNextClient(cl)) != NULL)
		if (cl->state != cs_free)
			SV_ClientCommand(cl, "spawnsoldiers\n");
}
示例#3
0
/**
 * @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);
}
示例#4
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;
	}
}
示例#5
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);
}
示例#6
0
/**
 * @brief If all connected clients have set their ready flag the server will spawn the clients
 * and that change the client state.
 * @sa SV_Spawn_f
 */
static void SV_CheckSpawnSoldiers (void)
{
    /* already started? */
    if (sv->spawned)
        return;

    client_t* cl = nullptr;
    while ((cl = SV_GetNextClient(cl)) != nullptr) {
        /* all players must be connected and all of them must have set
         * the ready flag */
        if (cl->state != cs_began || !cl->player->isReady())
            return;
    }

    sv->spawned = true;

    cl = nullptr;
    while ((cl = SV_GetNextClient(cl)) != nullptr)
        if (cl->state != cs_free)
            SV_ClientCommand(cl, CL_SPAWNSOLDIERS "\n");
}
示例#7
0
/**
 * @brief If all connected clients have set their ready flag the server will spawn the clients
 * and that change the client state.
 * @sa SV_Spawn_f
 */
static void SV_CheckSpawnSoldiers (void)
{
	client_t *cl;

	/* already started? */
	if (sv->spawned)
		return;

	cl = NULL;
	while ((cl = SV_GetNextClient(cl)) != NULL) {
		/* all players must be connected and all of them must have set
		 * the ready flag */
		if (cl->state != cs_began || !cl->player->isReady)
			return;
	}

	sv->spawned = true;

	cl = NULL;
	while ((cl = SV_GetNextClient(cl)) != NULL)
		if (cl->state != cs_free)
			SV_ClientCommand(cl, "spawnsoldiers\n");
}
示例#8
0
/**
 * @brief Sends the contents of msg to a subset of the clients, then frees msg
 * @param[in] mask Bitmask of the players to send the multicast to
 * @param[in,out] msg The message to send to the clients
 */
void SV_Multicast (int mask, const dbuffer &msg)
{
	/* send the data to all relevant clients */
	client_t *cl = nullptr;
	int j = -1;
	while ((cl = SV_GetNextClient(cl)) != nullptr) {
		j++;
		if (cl->state < cs_connected)
			continue;
		if (!(mask & (1 << j)))
			continue;

		/* write the message */
		NET_WriteConstMsg(cl->stream, msg);
	}
}
示例#9
0
/**
 * @brief Responds with short info for broadcast scans
 * @note The second parameter should be the current protocol version number.
 * @note Only a short server description - the user can determine whether he is
 * interested in a full status
 * @sa CL_ParseStatusMessage
 * @sa CL_ProcessPingReply
 */
static void SVC_Info (struct net_stream* s)
{
    if (SVC_RateLimitAddress(*s)) {
        Com_DPrintf(DEBUG_SERVER, "SVC_Info: 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_Info: rate limit exceeded, dropping request\n");
        return;
    }

    if (sv_maxclients->integer == 1) {
        Com_DPrintf(DEBUG_SERVER, "Ignore info string in singleplayer mode\n");
        return;	/* ignore in single player */
    }

    const int version = atoi(Cmd_Argv(1));
    if (version != PROTOCOL_VERSION) {
        char string[MAX_VAR];
        Com_sprintf(string, sizeof(string), "%s: wrong version (client: %i, host: %i)\n", sv_hostname->string, version, PROTOCOL_VERSION);
        NET_OOB_Printf(s, SV_CMD_PRINT "\n%s", string);
        return;
    }

    int count = 0;

    client_t* cl = nullptr;
    while ((cl = SV_GetNextClient(cl)) != nullptr)
        if (cl->state >= cs_spawning)
            count++;

    char infostring[MAX_INFO_STRING];
    infostring[0] = '\0';
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_protocol", DOUBLEQUOTE(PROTOCOL_VERSION));
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_hostname", sv_hostname->string);
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_dedicated", sv_dedicated->string);
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_gametype", sv_gametype->string);
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_mapname", sv->name);
    Info_SetValueForKeyAsInteger(infostring, sizeof(infostring), "clients", count);
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_maxclients", sv_maxclients->string);
    Info_SetValueForKey(infostring, sizeof(infostring), "sv_version", UFO_VERSION);
    NET_OOB_Printf(s, SV_CMD_INFO "\n%s", infostring);
}
示例#10
0
/**
 * @brief Returns the number of spawned players
 * @sa SV_ShutdownWhenEmpty
 */
int SV_CountPlayers (void)
{
	int count = 0;
	client_t *cl;

	if (!svs.initialized)
		return 0;

	cl = NULL;
	while ((cl = SV_GetNextClient(cl)) != NULL) {
		if (cl->state != cs_spawned)
			continue;

		count++;
	}

	return count;
}
示例#11
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);
		}
}
示例#12
0
/**
 * @brief Sends text to all active clients
 */
void SV_BroadcastPrintf (int level, const char *fmt, ...)
{
	va_list argptr;
	struct dbuffer *msg;
	client_t *cl;
	char str[1024];

	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);

	/* echo to console */
	if (sv_dedicated->integer) {
		char copy[1024];
		int i;
		const int length = sizeof(copy) - 1;

		va_start(argptr, fmt);
		Q_vsnprintf(copy, sizeof(copy), fmt, argptr);
		va_end(argptr);

		/* mask off high bits */
		for (i = 0; i < length && copy[i]; i++)
			copy[i] = copy[i] & 127;
		copy[i] = '\0';
		Com_Printf("%s", copy);
	}

	cl = NULL;
	while ((cl = SV_GetNextClient(cl)) != NULL) {
		if (level > cl->messagelevel)
			continue;
		if (cl->state < cs_connected)
			continue;
		NET_WriteConstMsg(cl->stream, msg);
	}

	free_dbuffer(msg);
}
示例#13
0
/**
 * @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);
}
示例#14
0
static void SV_CheckTimeouts (void)
{
    const int droppoint = svs.realtime - 1000 * sv_timeout->integer;

    if (sv_maxclients->integer == 1)
        return;

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

        /* might be invalid across a mapchange */
        if (cl->lastmessage > svs.realtime)
            cl->lastmessage = svs.realtime;

        if (cl->lastmessage > 0 && cl->lastmessage < droppoint)
            SV_DropClient(cl, "timed out");
    }
}
示例#15
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);
}
示例#16
0
/**
 * @brief Sends the contents of msg to a subset of the clients, then frees msg
 * @param[in] mask Bitmask of the players to send the multicast to
 * @param[in,out] msg The message to send to the clients
 */
void SV_Multicast (int mask, struct dbuffer *msg)
{
	client_t *cl;
	int j;

	/* send the data to all relevant clients */
	cl = NULL;
	j = -1;
	while ((cl = SV_GetNextClient(cl)) != NULL) {
		j++;
		if (cl->state < cs_connected)
			continue;
		if (!(mask & (1 << j)))
			continue;

		/* write the message */
		NET_WriteConstMsg(cl->stream, msg);
	}

	free_dbuffer(msg);
}
示例#17
0
/**
 * @brief Used by SV_Shutdown to send a final message to all
 * connected clients before the server goes down.
 * @sa SV_Shutdown
 */
static void SV_FinalMessage (const char *message, bool reconnect)
{
	client_t *cl;
	dbuffer msg(2 + strlen(message));

	if (reconnect)
		NET_WriteByte(&msg, svc_reconnect);
	else
		NET_WriteByte(&msg, svc_disconnect);
	NET_WriteString(&msg, message);

	cl = NULL;
	while ((cl = SV_GetNextClient(cl)) != NULL)
		if (cl->state >= cs_connected) {
			NET_WriteConstMsg(cl->stream, msg);
			NET_StreamFinished(cl->stream);
			cl->stream = NULL;
		}

	/* make sure, that this is send */
	NET_Wait(0);
}
示例#18
0
/**
 * @brief Responds with short info for broadcast scans
 * @note The second parameter should be the current protocol version number.
 * @note Only a short server description - the user can determine whether he is
 * interested in a full status
 * @sa CL_ParseStatusMessage
 * @sa CL_ProcessPingReply
 */
static void SVC_Info (struct net_stream *s)
{
	int version;

	if (sv_maxclients->integer == 1) {
		Com_DPrintf(DEBUG_SERVER, "Ignore info string in singleplayer mode\n");
		return;	/* ignore in single player */
	}

	version = atoi(Cmd_Argv(1));

	if (version != PROTOCOL_VERSION) {
		char string[MAX_VAR];
		Com_sprintf(string, sizeof(string), "%s: wrong version (client: %i, host: %i)\n", sv_hostname->string, version, PROTOCOL_VERSION);
		NET_OOB_Printf(s, "print\n%s", string);
	} else {
		client_t *cl;
		char infostring[MAX_INFO_STRING];
		int count = 0;

		cl = NULL;
		while ((cl = SV_GetNextClient(cl)) != NULL)
			if (cl->state >= cs_spawning)
				count++;

		infostring[0] = '\0';

		Info_SetValueForKey(infostring, sizeof(infostring), "sv_protocol", DOUBLEQUOTE(PROTOCOL_VERSION));
		Info_SetValueForKey(infostring, sizeof(infostring), "sv_hostname", sv_hostname->string);
		Info_SetValueForKey(infostring, sizeof(infostring), "sv_dedicated", sv_dedicated->string);
		Info_SetValueForKey(infostring, sizeof(infostring), "sv_gametype", sv_gametype->string);
		Info_SetValueForKey(infostring, sizeof(infostring), "sv_mapname", sv->name);
		Info_SetValueForKeyAsInteger(infostring, sizeof(infostring), "clients", count);
		Info_SetValueForKey(infostring, sizeof(infostring), "sv_maxclients", sv_maxclients->string);
		Info_SetValueForKey(infostring, sizeof(infostring), "sv_version", UFO_VERSION);
		NET_OOB_Printf(s, "info\n%s", infostring);
	}
}
示例#19
0
文件: sv_init.c 项目: kevlund/ufoai
/**
 * @brief Change the server to a new map, taking all connected clients along with it.
 * @note the full syntax is: @code map [day|night] [+]<map> [<assembly>] @endcode
 * @sa SV_AssembleMap
 * @sa CM_LoadMap
 * @sa Com_SetServerState
 */
void SV_Map (qboolean day, const char *levelstring, const char *assembly)
{
	int i;
	unsigned checksum = 0;
	char * map = SV_GetConfigString(CS_TILES);
	char * pos = SV_GetConfigString(CS_POSITIONS);
	mapInfo_t *randomMap = NULL;
	client_t *cl;

	/* any partially connected client will be restarted */
	Com_SetServerState(ss_restart);

	/* the game is just starting */
	SV_InitGame();

	if (!svs.initialized) {
		Com_Printf("Could not spawn the server\n");
		return;
	}

	assert(levelstring[0] != '\0');

	Com_DPrintf(DEBUG_SERVER, "SpawnServer: %s\n", levelstring);

	/* save name for levels that don't set message */
	SV_SetConfigString(CS_NAME, levelstring);
	SV_SetConfigString(CS_LIGHTMAP, day);

	Q_strncpyz(sv->name, levelstring, sizeof(sv->name));

	/* set serverinfo variable */
	sv_mapname = Cvar_FullSet("sv_mapname", sv->name, CVAR_SERVERINFO | CVAR_NOSET);

	/* notify the client in case of a listening server */
	SCR_BeginLoadingPlaque();

	if (assembly)
		Q_strncpyz(sv->assembly, assembly, sizeof(sv->assembly));
	else
		sv->assembly[0] = '\0';

	/* leave slots at start for clients only */
	cl = NULL;
	while ((cl = SV_GetNextClient(cl)) != NULL) {
		/* needs to reconnect */
		if (cl->state >= cs_spawning)
			SV_SetClientState(cl, cs_connected);
	}

	/* assemble and load the map */
	if (levelstring[0] == '+') {
		randomMap = SV_AssembleMap(levelstring + 1, assembly, map, pos, 0);
		if (!randomMap) {
			Com_Printf("Could not load assembly for map '%s'\n", levelstring);
			return;
		}
	} else {
		SV_SetConfigString(CS_TILES, levelstring);
		SV_SetConfigString(CS_POSITIONS, assembly ? assembly : "");
	}

	CM_LoadMap(map, day, pos, &sv->mapData, &sv->mapTiles);

	Com_Printf("checksum for the map '%s': %u\n", levelstring, sv->mapData.mapChecksum);
	SV_SetConfigString(CS_MAPCHECKSUM, sv->mapData.mapChecksum);

	checksum = Com_GetScriptChecksum();

	Com_Printf("ufo script checksum %u\n", checksum);
	SV_SetConfigString(CS_UFOCHECKSUM, checksum);
	SV_SetConfigString(CS_OBJECTAMOUNT, csi.numODs);
	SV_SetConfigString(CS_VERSION, UFO_VERSION);
	SV_SetConfigString(CS_MAPTITLE, SV_GetMapTitle(randomMap, levelstring));
	if (Q_strstart(SV_GetConfigString(CS_MAPTITLE), "b/")) {
		/* For base attack, CS_MAPTITLE contains too many chars */
		SV_SetConfigString(CS_MAPTITLE, "Base attack");
		SV_SetConfigString(CS_NAME, ".baseattack");
	}

	/* clear random-map assembly data */
	Mem_Free(randomMap);
	randomMap = NULL;

	/* clear physics interaction links */
	SV_ClearWorld();

	/* fix this! */
	for (i = 1; i <= sv->mapData.numInline; i++)
		sv->models[i] = CM_InlineModel(&sv->mapTiles, va("*%i", i));

	/* precache and static commands can be issued during map initialization */
	Com_SetServerState(ss_loading);

	TH_MutexLock(svs.serverMutex);
	/* load and spawn all other entities */
	svs.ge->SpawnEntities(sv->name, SV_GetConfigStringInteger(CS_LIGHTMAP), sv->mapData.mapEntityString);
	TH_MutexUnlock(svs.serverMutex);

	/* all precaches are complete */
	Com_SetServerState(ss_game);

	Com_Printf("-------------------------------------\n");

	Cbuf_CopyToDefer();
}
示例#20
0
/**
 * @brief A connection request that did not come from the master
 * @sa CL_ConnectionlessPacket
 */
static void SVC_DirectConnect (struct net_stream *stream)
{
	char userinfo[MAX_INFO_STRING];
	client_t *cl;
	player_t *player;
	int playernum;
	int version;
	bool connected;
	char buf[256];
	const char *peername = NET_StreamPeerToName(stream, buf, sizeof(buf), false);

	Com_DPrintf(DEBUG_SERVER, "SVC_DirectConnect()\n");

	if (sv->started || sv->spawned) {
		Com_Printf("rejected connect because match is already running\n");
		NET_OOB_Printf(stream, "print\nGame has started already.\n");
		return;
	}

	version = atoi(Cmd_Argv(1));
	if (version != PROTOCOL_VERSION) {
		Com_Printf("rejected connect from version %i - %s\n", version, peername);
		NET_OOB_Printf(stream, "print\nServer is version %s.\n", UFO_VERSION);
		return;
	}

	Q_strncpyz(userinfo, Cmd_Argv(2), sizeof(userinfo));

	if (userinfo[0] == '\0') {  /* catch empty userinfo */
		Com_Printf("Empty userinfo from %s\n", peername);
		NET_OOB_Printf(stream, "print\nConnection refused.\n");
		return;
	}

	if (strchr(userinfo, '\xFF')) {  /* catch end of message in string exploit */
		Com_Printf("Illegal userinfo contained xFF from %s\n", peername);
		NET_OOB_Printf(stream, "print\nConnection refused.\n");
		return;
	}

	if (strlen(Info_ValueForKey(userinfo, "ip"))) {  /* catch spoofed ips  */
		Com_Printf("Illegal userinfo contained ip from %s\n", peername);
		NET_OOB_Printf(stream, "print\nConnection refused.\n");
		return;
	}

	/* force the IP key/value pair so the game can filter based on ip */
	Info_SetValueForKey(userinfo, sizeof(userinfo), "ip", peername);

	/* find a client slot */
	cl = NULL;
	while ((cl = SV_GetNextClient(cl)) != NULL)
		if (cl->state == cs_free)
			break;
	if (cl == NULL) {
		NET_OOB_Printf(stream, "print\nServer is full.\n");
		Com_Printf("Rejected a connection - server is full.\n");
		return;
	}

	/* build a new connection - accept the new client
	 * this is the only place a client_t is ever initialized */
	OBJZERO(*cl);
	playernum = cl - SV_GetClient(0);
	player = PLAYER_NUM(playernum);
	cl->player = player;
	cl->player->num = playernum;

	{
		const ScopedMutex scopedMutex(svs.serverMutex);
		connected = svs.ge->ClientConnect(player, userinfo, sizeof(userinfo));
	}

	/* get the game a chance to reject this connection or modify the userinfo */
	if (!connected) {
		const char *rejmsg = Info_ValueForKey(userinfo, "rejmsg");
		if (rejmsg[0] != '\0') {
			NET_OOB_Printf(stream, "print\n%s\nConnection refused.\n", rejmsg);
			Com_Printf("Game rejected a connection from %s. Reason: %s\n", peername, rejmsg);
		} else {
			NET_OOB_Printf(stream, "print\nConnection refused.\n");
			Com_Printf("Game rejected a connection from %s.\n", peername);
		}
		return;
	}

	/* new player */
	cl->player->inuse = true;
	cl->lastmessage = svs.realtime;

	/* parse some info from the info strings */
	strncpy(cl->userinfo, userinfo, sizeof(cl->userinfo) - 1);
	SV_UserinfoChanged(cl);

	/* send the connect packet to the client */
	if (sv_http_downloadserver->string[0])
		NET_OOB_Printf(stream, "client_connect dlserver=%s", sv_http_downloadserver->string);
	else
		NET_OOB_Printf(stream, "client_connect");

	SV_SetClientState(cl, cs_connected);

	Q_strncpyz(cl->peername, peername, sizeof(cl->peername));
	cl->stream = stream;
	NET_StreamSetData(stream, cl);
}