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