1 /*
2 * metaserver_dialogs.cpp - UI for metaserver client
3
4 Copyright (C) 2004 and beyond by Woody Zenfell, III
5 and the "Aleph One" developers.
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 This license is contained in the file "COPYING",
18 which is included with this source code; it is available online at
19 http://www.gnu.org/licenses/gpl.html
20
21 April 29, 2004 (Woody Zenfell):
22 Created.
23 */
24
25 #if !defined(DISABLE_NETWORKING)
26
27 #include "cseries.h"
28
29 #include "alephversion.h"
30 #include "metaserver_dialogs.h"
31 #include "network_private.h" // GAME_PORT
32 #include "preferences.h"
33 #include "network_metaserver.h"
34 #include "map.h" // for _force_unique_teams!?!
35 #include "SoundManager.h"
36 #include "game_wad.h" // embedded physics/lua detection!!?!?!!
37
38 #include "Update.h"
39 #include "progress.h"
40
41 extern ChatHistory gMetaserverChatHistory;
42 extern MetaserverClient* gMetaserverClient;
43
44 const IPaddress
run_network_metaserver_ui()45 run_network_metaserver_ui()
46 {
47 return MetaserverClientUi::Create()->GetJoinAddressByRunning();
48 }
49
50
51 void
setupAndConnectClient(MetaserverClient & client)52 setupAndConnectClient(MetaserverClient& client)
53 {
54 {
55 client.setPlayerName(player_preferences->name);
56 }
57
58 // Check the updates URL for updates
59
60 static bool user_informed = false;
61
62 if (network_preferences->check_for_updates && !user_informed)
63 {
64 static bool first_check = true;
65
66 if (first_check)
67 {
68 uint32 ticks = SDL_GetTicks();
69
70 // if we get an update in a short amount of time, don't display progress
71 while (Update::instance()->GetStatus() == Update::CheckingForUpdate && SDL_GetTicks() - ticks < 500);
72
73 // check another couple seconds, but with a progress dialog
74 open_progress_dialog(_checking_for_updates);
75 while (Update::instance()->GetStatus() == Update::CheckingForUpdate && SDL_GetTicks() - ticks < 2500);
76 close_progress_dialog();
77 first_check = false;
78 }
79
80 Update::Status status = Update::instance()->GetStatus();
81 if (status == Update::UpdateAvailable)
82 {
83 dialog d;
84 vertical_placer *placer = new vertical_placer;
85
86 placer->dual_add(new w_title("UPDATE AVAILABLE"), d);
87 placer->add(new w_spacer(), true);
88
89 placer->dual_add(new w_static_text(expand_app_variables("An update for $appName$ is available.").c_str()), d);
90 #ifdef MAC_APP_STORE
91 placer->dual_add(new w_static_text("Please download it from the App Store"), d);
92 #else
93 placer->dual_add(new w_static_text("Please download it from"), d);
94 placer->dual_add(new w_hyperlink(A1_HOMEPAGE_URL), d);
95 #endif
96 placer->dual_add(new w_static_text("before playing games online."), d);
97
98 placer->add(new w_spacer(), true);
99 placer->dual_add(new w_button("OK", dialog_ok, &d), d);
100 d.set_widget_placer(placer);
101 d.run();
102 }
103
104 if (status != Update::CheckingForUpdate) user_informed = true;
105 }
106
107 client.setPlayerTeamName("");
108 client.connect(A1_METASERVER_HOST, 6321, network_preferences->metaserver_login, network_preferences->metaserver_password);
109 }
110
111
112
GameAvailableMetaserverAnnouncer(const game_info & info)113 GameAvailableMetaserverAnnouncer::GameAvailableMetaserverAnnouncer(const game_info& info)
114 {
115 setupAndConnectClient(*gMetaserverClient);
116
117 GameDescription description;
118 description.m_type = info.net_game_type;
119 // If the time limit is longer than a week, we figure it's untimed ( ;)
120 description.m_timeLimit = (info.time_limit > 7 * 24 * 3600 * TICKS_PER_SECOND) ? -1 : info.time_limit;
121 description.m_difficulty = info.difficulty_level;
122 description.m_mapName = string(info.level_name);
123 description.m_name = gMetaserverClient->playerName() + "'s Game";
124 description.m_teamsAllowed = !(info.game_options & _force_unique_teams);
125
126 // description's constructor gets scenario info, aleph one's protocol ID for us
127
128 description.m_alephoneBuildString = string(A1_DISPLAY_VERSION) + " (" + A1_DISPLAY_PLATFORM + ")";
129
130 bool HasPhysics, HasLua;
131 level_has_embedded_physics_lua(info.level_number, HasPhysics, HasLua);
132
133 if (network_preferences->use_netscript)
134 {
135 FileSpecifier netScript(network_preferences->netscript_file);
136 char netScriptName[256];
137 netScript.GetName(netScriptName);
138 netScriptName[32] = '\0';
139 description.m_netScript = netScriptName;
140 }
141 else if (HasLua)
142 {
143 description.m_netScript = "Embedded";
144 } // else constructor's blank string is desirable
145
146 description.m_hasGameOptions = true;
147 description.m_gameOptions = info.game_options;
148 description.m_cheatFlags = info.cheat_flags;
149 description.m_killLimit = info.kill_limit;
150
151 if (HasPhysics)
152 {
153 description.m_physicsName = "Embedded";
154 }
155 else
156 {
157 char name[256];
158 FileSpecifier fs = environment_preferences->map_file;
159 fs.GetName(name);
160 description.m_mapFileName = name;
161
162 fs = environment_preferences->physics_file;
163 if (fs.Exists() && fs.GetType() == _typecode_physics)
164 {
165 fs.GetName(name);
166 description.m_physicsName = name;
167 }
168 }
169
170 gMetaserverClient->announceGame(GAME_PORT, description);
171 }
172
Start(int32 time_limit)173 void GameAvailableMetaserverAnnouncer::Start(int32 time_limit)
174 {
175 gMetaserverClient->announceGameStarted(time_limit);
176
177 }
178
179 extern void PlayInterfaceButtonSound(short);
180
playersInRoomChanged(const std::vector<MetaserverPlayerInfo> & playerChanges)181 void GlobalMetaserverChatNotificationAdapter::playersInRoomChanged(const std::vector<MetaserverPlayerInfo>& playerChanges)
182 {
183 // print some notifications to the chat window
184 for (size_t i = 0; i < playerChanges.size(); i++)
185 {
186 if (playerChanges[i].verb() == MetaserverClient::PlayersInRoom::kAdd)
187 {
188 receivedLocalMessage(playerChanges[i].name() + "|p has joined the room");
189 }
190 else if (playerChanges[i].verb() == MetaserverClient::PlayersInRoom::kDelete)
191 {
192 receivedLocalMessage(playerChanges[i].name() + "|p has left the room");
193 }
194 }
195 }
196
gamesInRoomChanged(const std::vector<GameListMessage::GameListEntry> & gameChanges)197 void GlobalMetaserverChatNotificationAdapter::gamesInRoomChanged(const std::vector<GameListMessage::GameListEntry>& gameChanges)
198 {
199 for (size_t i = 0; i < gameChanges.size(); i++) {
200 if (gameChanges[i].verb() == MetaserverClient::GamesInRoom::kAdd) {
201 string name;
202 // find the player's name
203 for (size_t playerIndex = 0; playerIndex < gMetaserverClient->playersInRoom().size(); playerIndex++)
204 {
205 if (gMetaserverClient->playersInRoom()[playerIndex].id() == gameChanges[i].m_hostPlayerID) {
206 name = gMetaserverClient->playersInRoom()[playerIndex].name();
207 break;
208 }
209 }
210
211 if (name.size() > 0) {
212 receivedLocalMessage(gameChanges[i].format_for_chat(name));
213 }
214 }
215 }
216 }
217
color_entry(ColoredChatEntry & e,const MetaserverPlayerInfo * player)218 static void color_entry(ColoredChatEntry& e, const MetaserverPlayerInfo *player)
219 {
220 if (player)
221 {
222 e.color.red = player->color()[0];
223 e.color.green = player->color()[1];
224 e.color.blue = player->color()[2];
225 }
226 }
receivedChatMessage(const std::string & senderName,uint32 senderID,const std::string & message)227 void GlobalMetaserverChatNotificationAdapter::receivedChatMessage(const std::string& senderName, uint32 senderID, const std::string& message)
228 {
229 ColoredChatEntry e;
230 e.type = ColoredChatEntry::ChatMessage;
231 e.sender = senderName;
232 e.message = message;
233
234 color_entry(e, gMetaserverClient->find_player(senderID));
235
236 gMetaserverChatHistory.append(e);
237 PlayInterfaceButtonSound(_snd_computer_interface_logon);
238 }
239
receivedPrivateMessage(const std::string & senderName,uint32 senderID,const std::string & message)240 void GlobalMetaserverChatNotificationAdapter::receivedPrivateMessage(const std::string& senderName, uint32 senderID, const std::string& message)
241 {
242 ColoredChatEntry e;
243 e.type = ColoredChatEntry::PrivateMessage;
244 e.sender = senderName;
245 e.message = message;
246
247 color_entry(e, gMetaserverClient->find_player(senderID));
248
249 gMetaserverChatHistory.append(e);
250 PlayInterfaceButtonSound(_snd_compiler_projectile_flyby);
251 }
252
receivedBroadcastMessage(const std::string & message)253 void GlobalMetaserverChatNotificationAdapter::receivedBroadcastMessage(const std::string& message)
254 {
255 ColoredChatEntry e;
256 e.type = ColoredChatEntry::ServerMessage;
257 e.message = message;
258
259 gMetaserverChatHistory.append(e);
260 }
261
receivedLocalMessage(const std::string & message)262 void GlobalMetaserverChatNotificationAdapter::receivedLocalMessage(const std::string& message)
263 {
264 ColoredChatEntry e;
265 e.type = ColoredChatEntry::LocalMessage;
266 e.message = message;
267
268 gMetaserverChatHistory.append(e);
269 }
270
roomDisconnected()271 void GlobalMetaserverChatNotificationAdapter::roomDisconnected()
272 {
273 ColoredChatEntry e;
274 e.type = ColoredChatEntry::LocalMessage;
275 e.message = "|iConnection to room lost.";
276
277 gMetaserverChatHistory.append(e);
278 }
279
delete_widgets()280 void MetaserverClientUi::delete_widgets ()
281 {
282 delete m_playersInRoomWidget;
283 delete m_gamesInRoomWidget;
284 delete m_chatEntryWidget;
285 delete m_chatWidget;
286 delete m_cancelWidget;
287 delete m_muteWidget;
288 delete m_joinWidget;
289 delete m_gameInfoWidget;
290 }
291
GetJoinAddressByRunning()292 const IPaddress MetaserverClientUi::GetJoinAddressByRunning()
293 {
294 // This was designed with one-shot-ness in mind
295 assert(!m_used);
296 m_used = true;
297
298 obj_clear(m_joinAddress);
299
300 setupAndConnectClient(*gMetaserverClient);
301 gMetaserverClient->associateNotificationAdapter(this);
302
303 m_gamesInRoomWidget->SetItemSelectedCallback(bind(&MetaserverClientUi::GameSelected, this, _1));
304 m_playersInRoomWidget->SetItemSelectedCallback(bind(&MetaserverClientUi::PlayerSelected, this, _1));
305 m_muteWidget->set_callback(boost::bind(&MetaserverClientUi::MuteClicked, this));
306 m_chatEntryWidget->set_callback(bind(&MetaserverClientUi::ChatTextEntered, this, _1));
307 m_cancelWidget->set_callback(boost::bind(&MetaserverClientUi::handleCancel, this));
308 m_joinWidget->set_callback(boost::bind(&MetaserverClientUi::JoinClicked, this));
309 m_gameInfoWidget->set_callback(boost::bind(&MetaserverClientUi::InfoClicked, this));
310
311 gMetaserverChatHistory.clear ();
312 m_chatWidget->attachHistory (&gMetaserverChatHistory);
313
314 if (Run() < 0) {
315 handleCancel();
316 }
317
318 return m_joinAddress;
319 }
320
GameSelected(GameListMessage::GameListEntry game)321 void MetaserverClientUi::GameSelected(GameListMessage::GameListEntry game)
322 {
323 if (gMetaserverClient->game_target() == game.id())
324 {
325 if (SDL_GetTicks() - m_lastGameSelected < 333 && (!game.running() && Scenario::instance()->IsCompatible(game.m_description.m_scenarioID)))
326 {
327 JoinClicked();
328 }
329 else
330 {
331 gMetaserverClient->game_target(GameListMessage::GameListEntry::IdNone);
332 }
333 }
334 else
335 {
336 m_lastGameSelected = SDL_GetTicks();
337 gMetaserverClient->game_target(game.id());
338 }
339
340 std::vector<GameListMessage::GameListEntry> sortedGames = gMetaserverClient->gamesInRoom();
341 std::sort(sortedGames.begin(), sortedGames.end(), GameListMessage::GameListEntry::sort);
342 m_gamesInRoomWidget->SetItems(sortedGames);
343 UpdateGameButtons();
344 }
345
UpdatePlayerButtons()346 void MetaserverClientUi::UpdatePlayerButtons()
347 {
348 if (gMetaserverClient->player_target() == MetaserverPlayerInfo::IdNone)
349 {
350 m_muteWidget->deactivate();
351 }
352 else
353 {
354 m_muteWidget->activate();
355 }
356 }
357
UpdateGameButtons()358 void MetaserverClientUi::UpdateGameButtons()
359 {
360 if (gMetaserverClient->game_target() == GameListMessage::GameListEntry::IdNone)
361 {
362 m_joinWidget->deactivate();
363 m_gameInfoWidget->deactivate();
364 }
365 else
366 {
367 const GameListMessage::GameListEntry *game = gMetaserverClient->find_game(gMetaserverClient->game_target());
368 if (game)
369 {
370 m_gameInfoWidget->activate();
371 if (!game->running() && Scenario::instance()->IsCompatible(game->m_description.m_scenarioID))
372 {
373 m_joinWidget->activate();
374 }
375 else
376 {
377 m_joinWidget->deactivate();
378 }
379 }
380 else
381 {
382 // huh?
383 m_gameInfoWidget->deactivate();
384 m_joinWidget->deactivate();
385 }
386 }
387 }
388
PlayerSelected(MetaserverPlayerInfo info)389 void MetaserverClientUi::PlayerSelected(MetaserverPlayerInfo info)
390 {
391 if (gMetaserverClient->player_target() == info.id())
392 {
393 gMetaserverClient->player_target(MetaserverPlayerInfo::IdNone);
394 }
395 else
396 {
397 gMetaserverClient->player_target(info.id());
398 if (SDL_GetModState() & KMOD_CTRL)
399 {
400 m_stay_selected = true;
401 }
402 else
403 {
404 m_stay_selected = false;
405 }
406 }
407
408 std::vector<MetaserverPlayerInfo> sortedPlayers = gMetaserverClient->playersInRoom();
409 std::sort(sortedPlayers.begin(), sortedPlayers.end(), MetaserverPlayerInfo::sort);
410
411 m_playersInRoomWidget->SetItems(sortedPlayers);
412 UpdatePlayerButtons();
413 }
414
MuteClicked()415 void MetaserverClientUi::MuteClicked()
416 {
417 gMetaserverClient->ignore(gMetaserverClient->player_target());
418 }
419
JoinClicked()420 void MetaserverClientUi::JoinClicked()
421 {
422 const GameListMessage::GameListEntry *game = gMetaserverClient->find_game(gMetaserverClient->game_target());
423 if (game)
424 {
425 JoinGame(*game);
426 }
427 }
428
JoinGame(const GameListMessage::GameListEntry & game)429 void MetaserverClientUi::JoinGame(const GameListMessage::GameListEntry& game)
430 {
431 memcpy(&m_joinAddress.host, &game.m_ipAddress, sizeof(m_joinAddress.host));
432 m_joinAddress.port = game.m_port;
433 Stop();
434 }
435
playersInRoomChanged(const std::vector<MetaserverPlayerInfo> & playerChanges)436 void MetaserverClientUi::playersInRoomChanged(const std::vector<MetaserverPlayerInfo> &playerChanges)
437 {
438 std::vector<MetaserverPlayerInfo> sortedPlayers = gMetaserverClient->playersInRoom();
439 std::sort(sortedPlayers.begin(), sortedPlayers.end(), MetaserverPlayerInfo::sort);
440
441 m_playersInRoomWidget->SetItems(sortedPlayers);
442 UpdatePlayerButtons();
443 GlobalMetaserverChatNotificationAdapter::playersInRoomChanged(playerChanges);
444
445 }
446
gamesInRoomChanged(const std::vector<GameListMessage::GameListEntry> & gameChanges)447 void MetaserverClientUi::gamesInRoomChanged(const std::vector<GameListMessage::GameListEntry> &gameChanges)
448 {
449 std::vector<GameListMessage::GameListEntry> sortedGames = gMetaserverClient->gamesInRoom();
450 std::sort(sortedGames.begin(), sortedGames.end(), GameListMessage::GameListEntry::sort);
451 m_gamesInRoomWidget->SetItems(sortedGames);
452 UpdateGameButtons();
453 GlobalMetaserverChatNotificationAdapter::gamesInRoomChanged(gameChanges);
454 for (size_t i = 0; i < gameChanges.size(); i++)
455 {
456 if (gameChanges[i].verb() == MetaserverClient::GamesInRoom::kAdd && !gameChanges[i].running())
457 {
458 PlayInterfaceButtonSound(_snd_got_ball);
459 break;
460 }
461 }
462 }
463
sendChat()464 void MetaserverClientUi::sendChat()
465 {
466 string message = m_chatEntryWidget->get_text();
467 if (gMetaserverClient->player_target() != MetaserverPlayerInfo::IdNone)
468 {
469 gMetaserverClient->sendPrivateMessage(gMetaserverClient->player_target(), message);
470 if (!m_stay_selected)
471 gMetaserverClient->player_target(MetaserverPlayerInfo::IdNone);
472 std::vector<MetaserverPlayerInfo> sortedPlayers = gMetaserverClient->playersInRoom();
473 std::sort(sortedPlayers.begin(), sortedPlayers.end(), MetaserverPlayerInfo::sort);
474
475 m_playersInRoomWidget->SetItems(sortedPlayers);
476 UpdatePlayerButtons();
477 }
478 else
479 gMetaserverClient->sendChatMessage(message);
480 m_chatEntryWidget->set_text(string());
481 }
482
ChatTextEntered(char character)483 void MetaserverClientUi::ChatTextEntered (char character)
484 {
485 if (character == '\r')
486 sendChat();
487 }
488
handleCancel()489 void MetaserverClientUi::handleCancel ()
490 {
491 // gMetaserverClient->disconnect ();
492 delete gMetaserverClient;
493 gMetaserverClient = new MetaserverClient ();
494 Stop ();
495 }
496
497 #endif // !defined(DISABLE_NETWORKING)
498