1 /**
2  * @file
3  * @brief Serverlist menu callbacks for multiplayer
4  */
5 
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 */
25 
26 #include "../../cl_shared.h"
27 #include "../../ui/ui_data.h"
28 #include "mp_callbacks.h"
29 #include "mp_serverlist.h"
30 #include "../cl_game.h"
31 
32 static const cgame_import_t* cgi;
33 
34 teamData_t teamData;
35 cvar_t* rcon_client_password;
36 cvar_t* cl_maxsoldiersperteam;
37 cvar_t* cl_maxsoldiersperplayer;
38 cvar_t* cl_roundtimelimit;
39 static cvar_t* rcon_address;
40 static cvar_t* info_password;
41 
GAME_MP_Connect_f(void)42 static void GAME_MP_Connect_f (void)
43 {
44 	if (!selectedServer && cgi->Cmd_Argc() != 2 && cgi->Cmd_Argc() != 3) {
45 		cgi->Com_Printf("Usage: %s <server> [<port>]\n", cgi->Cmd_Argv(0));
46 		return;
47 	}
48 
49 	char server[MAX_VAR];
50 	char serverport[16];
51 	if (cgi->Cmd_Argc() == 2) {
52 		Q_strncpyz(server, cgi->Cmd_Argv(1), sizeof(server));
53 		Q_strncpyz(serverport, DOUBLEQUOTE(PORT_SERVER), sizeof(serverport));
54 	} else if (cgi->Cmd_Argc() == 3) {
55 		Q_strncpyz(server, cgi->Cmd_Argv(1), sizeof(server));
56 		Q_strncpyz(serverport, cgi->Cmd_Argv(2), sizeof(serverport));
57 	} else {
58 		assert(selectedServer);
59 		Q_strncpyz(server, selectedServer->node, sizeof(server));
60 		Q_strncpyz(serverport, selectedServer->service, sizeof(serverport));
61 	}
62 
63 	if (cgi->GAME_IsTeamEmpty() && !cgi->GAME_LoadDefaultTeam(true)) {
64 		cgi->UI_Popup(_("Error"), "%s", _("Assemble a team first"));
65 		return;
66 	}
67 
68 	if (cgi->Cvar_GetInteger("mn_server_need_password")) {
69 		cgi->UI_PushWindow("serverpassword");
70 		return;
71 	}
72 
73 	/* if running a local server, kill it and reissue */
74 	cgi->SV_Shutdown("Server quit.", false);
75 	cgi->CL_Disconnect();
76 
77 	cgi->GAME_SetServerInfo(server, serverport);
78 
79 	cgi->CL_SetClientState(ca_connecting);
80 
81 	cgi->HUD_InitUI("multiplayerInGame");
82 }
83 
GAME_MP_RconCallback(struct net_stream * s)84 static void GAME_MP_RconCallback (struct net_stream *s)
85 {
86 	AutoPtr<dbuffer> buf(cgi->NET_ReadMsg(s));
87 	if (!buf) {
88 		cgi->NET_StreamFree(s);
89 		return;
90 	}
91 	const int cmd = cgi->NET_ReadByte(buf);
92 	if (cmd != svc_oob) {
93 		cgi->NET_StreamFree(s);
94 		return;
95 	}
96 	char commandBuf[8];
97 	cgi->NET_ReadStringLine(buf, commandBuf, sizeof(commandBuf));
98 
99 	if (Q_streq(commandBuf, "print")) {
100 		char paramBuf[2048];
101 		cgi->NET_ReadString(buf, paramBuf, sizeof(paramBuf));
102 		cgi->Com_Printf("%s\n", paramBuf);
103 	}
104 	cgi->NET_StreamFree(s);
105 }
106 
107 /**
108  * @brief Sends an rcon command to the gameserver that the user is currently connected to,
109  * or if this is not the case, the gameserver that is specified in the cvar rcon_address.
110  * @return @c true if the command was send, @c false otherwise.
111  */
GAME_MP_Rcon(const char * password,const char * command)112 bool GAME_MP_Rcon (const char* password, const char* command)
113 {
114 	if (Q_strnull(password)) {
115 		cgi->Com_Printf("You must set 'rcon_password' before issuing a rcon command.\n");
116 		return false;
117 	}
118 
119 	if (cgi->CL_GetClientState() >= ca_connected) {
120 		cgi->NET_OOB_Printf2(SV_CMD_RCON " %s %s", password, command);
121 		return true;
122 	} else if (rcon_address->string) {
123 		const char* port;
124 
125 		if (strstr(rcon_address->string, ":"))
126 			port = strstr(rcon_address->string, ":") + 1;
127 		else
128 			port = DOUBLEQUOTE(PORT_SERVER);
129 
130 		struct net_stream *s = cgi->NET_Connect(rcon_address->string, port, nullptr);
131 		if (s) {
132 			cgi->NET_OOB_Printf(s, SV_CMD_RCON " %s %s", password, command);
133 			cgi->NET_StreamSetCallback(s, &GAME_MP_RconCallback);
134 			return true;
135 		}
136 	}
137 
138 	cgi->Com_Printf("You are not connected to any server\n");
139 	return false;
140 }
141 
142 /**
143  * Send the rest of the command line over as
144  * an unconnected command.
145  */
GAME_MP_Rcon_f(void)146 static void GAME_MP_Rcon_f (void)
147 {
148 	if (cgi->Cmd_Argc() < 2) {
149 		cgi->Com_Printf("Usage: %s <command>\n", cgi->Cmd_Argv(0));
150 		return;
151 	}
152 
153 	if (!rcon_client_password->string) {
154 		cgi->Com_Printf("You must set 'rcon_password' before issuing a rcon command.\n");
155 		return;
156 	}
157 
158 	if (!GAME_MP_Rcon(rcon_client_password->string, cgi->Cmd_Args()))
159 		Com_Printf("Could not send the rcon command\n");
160 }
161 
GAME_MP_StartGame_f(void)162 static void GAME_MP_StartGame_f (void)
163 {
164 	if (cgi->Com_ServerState())
165 		cgi->Cmd_ExecuteString("startgame");
166 	else
167 		cgi->Cmd_ExecuteString("rcon startgame");
168 }
169 
170 /**
171  * @brief Binding for disconnect console command
172  */
GAME_MP_Disconnect_f(void)173 static void GAME_MP_Disconnect_f (void)
174 {
175 	cgi->SV_ShutdownWhenEmpty();
176 	cgi->CL_Drop();
177 }
178 
179 /**
180  * @brief The server is changing levels
181  */
GAME_MP_Reconnect_f(void)182 static void GAME_MP_Reconnect_f (void)
183 {
184 	if (cgi->Com_ServerState())
185 		return;
186 
187 	if (cgi->CL_GetClientState() >= ca_connecting) {
188 		cgi->Com_Printf("disconnecting...\n");
189 		cgi->CL_Disconnect();
190 	}
191 
192 	cgi->CL_SetClientState(ca_connecting);
193 	cgi->Com_Printf("reconnecting...\n");
194 }
195 
196 /**
197  * @brief Send the SV_CMD_TEAMINFO command to server
198  * @sa CL_ParseTeamInfoMessage
199  * @sa SV_ConnectionlessPacket
200  */
GAME_MP_SelectTeam_Init_f(void)201 static void GAME_MP_SelectTeam_Init_f (void)
202 {
203 	/* reset menu text */
204 	cgi->UI_ResetData(TEXT_STANDARD);
205 
206 	if (cgi->Com_ServerState())
207 		cgi->Cvar_Set("cl_admin", "1");
208 	else
209 		cgi->Cvar_Set("cl_admin", "0");
210 
211 	cgi->NET_OOB_Printf2(SV_CMD_TEAMINFO " %i", PROTOCOL_VERSION);
212 	cgi->UI_RegisterText(TEXT_STANDARD, _("Select a free team or your coop team"));
213 }
214 
GAME_MP_SetTeamNum(int teamnum)215 static bool GAME_MP_SetTeamNum (int teamnum)
216 {
217 	if (teamData.maxPlayersPerTeam > teamData.teamCount[teamnum]) {
218 		static char buf[MAX_VAR];
219 		cgi->Cvar_SetValue("cl_teamnum", teamnum);
220 		Com_sprintf(buf, sizeof(buf), _("Current team: %i"), teamnum);
221 		cgi->UI_RegisterText(TEXT_STANDARD, buf);
222 		return true;
223 	}
224 
225 	cgi->UI_RegisterText(TEXT_STANDARD, _("Team is already in use"));
226 	cgi->Com_DPrintf(DEBUG_CLIENT, "team %i is already in use: %i (max: %i)\n",
227 		teamnum, teamData.teamCount[teamnum], teamData.maxPlayersPerTeam);
228 	return false;
229 }
230 
231 /**
232  * @brief Increase or decrease the teamnum
233  * @sa GAME_MP_SelectTeam_Init_f
234  */
GAME_MP_TeamNum_f(void)235 static void GAME_MP_TeamNum_f (void)
236 {
237 	int i = cgi->Cvar_GetInteger("cl_teamnum");
238 
239 	if (i <= TEAM_CIVILIAN || i > teamData.maxteams) {
240 		cgi->Cvar_SetValue("cl_teamnum", TEAM_DEFAULT);
241 		i = TEAM_DEFAULT;
242 	}
243 
244 	if (Q_streq(cgi->Cmd_Argv(0), "teamnum_dec")) {
245 		for (i--; i > TEAM_CIVILIAN; i--) {
246 			if (GAME_MP_SetTeamNum(i))
247 				break;
248 		}
249 	} else {
250 		for (i++; i <= teamData.maxteams; i++) {
251 			if (GAME_MP_SetTeamNum(i))
252 				break;
253 		}
254 	}
255 
256 	GAME_MP_SelectTeam_Init_f();
257 }
258 
259 /**
260  * @brief Autocomplete function for some network functions
261  * @sa Cmd_AddParamCompleteFunction
262  * @todo Extend this for all the servers on the server browser list
263  */
GAME_MP_CompleteNetworkAddress(const char * partial,const char ** match)264 static int GAME_MP_CompleteNetworkAddress (const char* partial, const char** match)
265 {
266 	int n = 0;
267 	for (int i = 0; i != MAX_BOOKMARKS; ++i) {
268 		char const* const adrStr = cgi->Cvar_GetString(va("adr%i", i));
269 		if (adrStr[0] != '\0' && cgi->Cmd_GenericCompleteFunction(adrStr, partial, match)) {
270 			cgi->Com_Printf("%s\n", adrStr);
271 			++n;
272 		}
273 	}
274 	return n;
275 }
276 
GAME_MP_InitUI_f(void)277 static void GAME_MP_InitUI_f (void)
278 {
279 	uiNode_t* gameTypes = nullptr;
280 	for (int i = 0; i < cgi->csi->numGTs; i++) {
281 		const gametype_t* gt = &cgi->csi->gts[i];
282 		cgi->UI_AddOption(&gameTypes, gt->id, _(gt->name), gt->id);
283 	}
284 	cgi->UI_RegisterOption(TEXT_LIST, gameTypes);
285 }
286 
GAME_MP_CallbacksInit(const cgame_import_t * import)287 void GAME_MP_CallbacksInit (const cgame_import_t* import)
288 {
289 	cgi = import;
290 	rcon_client_password = cgi->Cvar_Get("rcon_password", "", 0, "Remote console password");
291 	rcon_address = cgi->Cvar_Get("rcon_address", "", 0, "Address of the host you would like to control via rcon");
292 	info_password = cgi->Cvar_Get("password", "", CVAR_USERINFO, nullptr);
293 	cl_maxsoldiersperteam = cgi->Cvar_Get("sv_maxsoldiersperteam", "4", CVAR_ARCHIVE | CVAR_SERVERINFO, "How many soldiers may one team have");
294 	cl_maxsoldiersperplayer = cgi->Cvar_Get("sv_maxsoldiersperplayer", DOUBLEQUOTE(MAX_ACTIVETEAM), CVAR_ARCHIVE | CVAR_SERVERINFO, "How many soldiers one player is able to control in a given team");
295 	cl_roundtimelimit = cgi->Cvar_Get("sv_roundtimelimit", "90", CVAR_ARCHIVE | CVAR_SERVERINFO, "Timelimit in seconds for multiplayer rounds");
296 	cgi->Cmd_AddCommand("mp_selectteam_init", GAME_MP_SelectTeam_Init_f, "Function that gets all connected players and let you choose a free team");
297 	cgi->Cmd_AddCommand("mp_init_ui", GAME_MP_InitUI_f, nullptr);
298 	cgi->Cmd_AddCommand("teamnum_dec", GAME_MP_TeamNum_f, "Decrease the preferred teamnum");
299 	cgi->Cmd_AddCommand("teamnum_inc", GAME_MP_TeamNum_f, "Increase the preferred teamnum");
300 	cgi->Cmd_AddCommand("pingservers", GAME_MP_PingServers_f, "Ping all servers in local network to get the serverlist");
301 	cgi->Cmd_AddCommand("disconnect", GAME_MP_Disconnect_f, "Disconnect from the current server");
302 	cgi->Cmd_AddCommand("connect", GAME_MP_Connect_f, "Connect to given ip");
303 	cgi->Cmd_AddParamCompleteFunction("connect", GAME_MP_CompleteNetworkAddress);
304 	cgi->Cmd_AddCommand("reconnect", GAME_MP_Reconnect_f, "Reconnect to last server");
305 	cgi->Cmd_AddCommand("rcon", GAME_MP_Rcon_f, "Execute a rcon command - see rcon_password");
306 	cgi->Cmd_AddCommand("cl_startgame", GAME_MP_StartGame_f, "Forces a gamestart if you are the admin");
307 	cgi->Cmd_AddParamCompleteFunction("rcon", GAME_MP_CompleteNetworkAddress);
308 
309 	cl_maxsoldiersperteam->modified = false;
310 	cl_maxsoldiersperplayer->modified = false;
311 }
312 
GAME_MP_CallbacksShutdown(void)313 void GAME_MP_CallbacksShutdown (void)
314 {
315 	cgi->Cmd_RemoveCommand("mp_selectteam_init");
316 	cgi->Cmd_RemoveCommand("mp_init_ui");
317 	cgi->Cmd_RemoveCommand("teamnum_dec");
318 	cgi->Cmd_RemoveCommand("teamnum_inc");
319 	cgi->Cmd_RemoveCommand("rcon");
320 	cgi->Cmd_RemoveCommand("pingservers");
321 	cgi->Cmd_RemoveCommand("disconnect");
322 	cgi->Cmd_RemoveCommand("connect");
323 	cgi->Cmd_RemoveCommand("reconnect");
324 
325 	cgi->Cvar_Delete("rcon_address");
326 }
327