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"); }
/** * @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"); }
/** * @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); }
/** * @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; } }
/** * @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); }
/** * @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"); }
/** * @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"); }
/** * @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); } }
/** * @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); }
/** * @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; }
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); } }
/** * @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); }
/** * @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); }
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"); } }
/** * @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); }
/** * @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); }
/** * @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); }
/** * @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); } }
/** * @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(); }
/** * @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); }