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