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