1 //
2 // SuperTuxKart - a fun racing game with go-kart
3 // Copyright (C) 2013-2015 SuperTuxKart-Team
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 3
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 #include "network/protocols/client_lobby.hpp"
20
21 #include "addons/addons_manager.hpp"
22 #include "audio/music_manager.hpp"
23 #include "audio/sfx_manager.hpp"
24 #include "config/user_config.hpp"
25 #include "config/player_manager.hpp"
26 #include "graphics/camera.hpp"
27 #include "guiengine/engine.hpp"
28 #include "guiengine/modaldialog.hpp"
29 #include "guiengine/message_queue.hpp"
30 #include "guiengine/screen_keyboard.hpp"
31 #include "input/device_manager.hpp"
32 #include "input/input_device.hpp"
33 #include "items/network_item_manager.hpp"
34 #include "items/powerup_manager.hpp"
35 #include "karts/abstract_kart.hpp"
36 #include "karts/controller/controller.hpp"
37 #include "karts/kart_properties.hpp"
38 #include "karts/kart_properties_manager.hpp"
39 #include "modes/linear_world.hpp"
40 #include "network/crypto.hpp"
41 #include "network/event.hpp"
42 #include "network/game_setup.hpp"
43 #include "network/network_config.hpp"
44 #include "network/network_player_profile.hpp"
45 #include "network/network_timer_synchronizer.hpp"
46 #include "network/peer_vote.hpp"
47 #include "network/protocols/connect_to_server.hpp"
48 #include "network/protocols/game_protocol.hpp"
49 #include "network/protocols/game_events_protocol.hpp"
50 #include "network/protocols/server_lobby.hpp"
51 #include "network/protocol_manager.hpp"
52 #include "network/race_event_manager.hpp"
53 #include "network/server.hpp"
54 #include "network/server_config.hpp"
55 #include "network/stk_host.hpp"
56 #include "network/stk_peer.hpp"
57 #include "online/online_profile.hpp"
58 #include "online/xml_request.hpp"
59 #include "states_screens/dialogs/addons_pack.hpp"
60 #include "states_screens/online/networking_lobby.hpp"
61 #include "states_screens/online/network_kart_selection.hpp"
62 #include "states_screens/online/tracks_screen.hpp"
63 #include "tracks/track.hpp"
64 #include "tracks/track_manager.hpp"
65 #include "utils/log.hpp"
66 #include "utils/string_utils.hpp"
67 #include "utils/translation.hpp"
68
69 #include <algorithm>
70 #include <cstdlib>
71
72 // ============================================================================
73 /** The protocol that manages starting a race with the server. It uses a
74 * finite state machine:
75 \dot
76 digraph interaction {
77 "NONE" -> "LINKED" [label="ENet connection with server established"]
78 "LINKED" -> "REQUESTING_CONNECTION" [label="Request connection from server"]
79 "REQUESTING_CONNECTION" -> CONNECTED [label="Connection accepted by server"]
80 "REQUESTING_CONNECTION" -> "?? TO BE DONE ??" [label="Connection refused by server"]
81 "CONNECTED" -> "KART_SELECTION" [label="Server tells us to start kart selection"]
82 "KART_SELECTION" -> "SELECTING_KARTS" [label="Show kart selection screen"]
83 "SELECTING_KARTS" -> "RACING" [label="Server sends start race message"]
84 }
85 \enddot
86 Note that some states are actually managed outside of the client lobby. For
87 example to select race details after selecting a kart is managed by the GUI
88 engine.
89
90 */
91
ClientLobby(std::shared_ptr<Server> s)92 ClientLobby::ClientLobby(std::shared_ptr<Server> s)
93 : LobbyProtocol()
94 {
95 m_auto_started = false;
96 m_waiting_for_game = false;
97 m_server_auto_game_time = false;
98 m_received_server_result = false;
99 m_state.store(NONE);
100 m_server = s;
101 setHandleDisconnections(true);
102 m_disconnected_msg[PDI_TIMEOUT] = _("Server connection timed out.");
103 m_disconnected_msg[PDI_NORMAL] = _("Server has been shut down.");
104 m_disconnected_msg[PDI_KICK] = _("You were kicked from the server.");
105 m_disconnected_msg[PDI_KICK_HIGH_PING] =
106 _("You were kicked: Ping too high.");
107 m_first_connect = true;
108 m_spectator = false;
109 m_server_live_joinable = false;
110 m_server_send_live_load_world = false;
111 m_server_enabled_chat = true;
112 m_server_enabled_track_voting = true;
113 m_server_enabled_report_player = false;
114 } // ClientLobby
115
116 //-----------------------------------------------------------------------------
~ClientLobby()117 ClientLobby::~ClientLobby()
118 {
119 if (m_server->supportsEncryption())
120 {
121 auto request = std::make_shared<Online::XMLRequest>();
122 NetworkConfig::get()->setServerDetails(request,
123 "clear-user-joined-server");
124 request->queue();
125 }
126 } // ClientLobby
127
128 //-----------------------------------------------------------------------------
setup()129 void ClientLobby::setup()
130 {
131 m_ranking_changes.clear();
132 m_spectator = false;
133 m_server_send_live_load_world = false;
134 m_auto_back_to_lobby_time = std::numeric_limits<uint64_t>::max();
135 m_start_live_game_time = std::numeric_limits<uint64_t>::max();
136 m_received_server_result = false;
137 if (!GUIEngine::isNoGraphics())
138 TracksScreen::getInstance()->resetVote();
139 LobbyProtocol::setup();
140 // The client lobby is only created when connected to server
141 m_state.store(LINKED);
142 } // setup
143
144 //-----------------------------------------------------------------------------
145 /** Called from the gui when a client clicked on 'continue' on the race result
146 * screen. It notifies the server that this client has exited the screen and
147 * is back at the lobby.
148 */
doneWithResults()149 void ClientLobby::doneWithResults()
150 {
151 NetworkString* done = getNetworkString(1);
152 done->setSynchronous(true);
153 done->addUInt8(LE_RACE_FINISHED_ACK);
154 sendToServer(done, /*reliable*/true);
155 delete done;
156 } // doneWithResults
157
158 //-----------------------------------------------------------------------------
notifyEvent(Event * event)159 bool ClientLobby::notifyEvent(Event* event)
160 {
161 assert(m_game_setup); // assert that the setup exists
162 if (event->getType() != EVENT_TYPE_MESSAGE)
163 return true;
164
165 NetworkString &data = event->data();
166 assert(data.size()); // assert that data isn't empty
167 uint8_t message_type = data.getUInt8();
168 Log::info("ClientLobby", "Synchronous message of type %d",
169 message_type);
170 switch(message_type)
171 {
172 case LE_START_SELECTION: startSelection(event); break;
173 case LE_LOAD_WORLD: addAllPlayers(event); break;
174 case LE_RACE_FINISHED: raceFinished(event); break;
175 case LE_BACK_LOBBY: backToLobby(event); break;
176 case LE_UPDATE_PLAYER_LIST: updatePlayerList(event); break;
177 case LE_CHAT: handleChat(event); break;
178 case LE_CONNECTION_ACCEPTED: connectionAccepted(event); break;
179 case LE_SERVER_INFO: handleServerInfo(event); break;
180 case LE_PLAYER_DISCONNECTED : disconnectedPlayer(event); break;
181 case LE_CONNECTION_REFUSED: connectionRefused(event); break;
182 case LE_VOTE: receivePlayerVote(event); break;
183 case LE_SERVER_OWNERSHIP: becomingServerOwner(); break;
184 case LE_BAD_TEAM: handleBadTeam(); break;
185 case LE_BAD_CONNECTION: handleBadConnection(); break;
186 case LE_LIVE_JOIN_ACK: liveJoinAcknowledged(event); break;
187 case LE_KART_INFO: handleKartInfo(event); break;
188 case LE_START_RACE: startGame(event); break;
189 case LE_REPORT_PLAYER: reportSuccess(event); break;
190 default:
191 break;
192 } // switch
193 return true;
194 } // notifyEvent
195
196 //-----------------------------------------------------------------------------
notifyEventAsynchronous(Event * event)197 bool ClientLobby::notifyEventAsynchronous(Event* event)
198 {
199 assert(m_game_setup); // assert that the setup exists
200 if (event->getType() == EVENT_TYPE_MESSAGE)
201 {
202 NetworkString &data = event->data();
203 assert(data.size()); // assert that data isn't empty
204 uint8_t message_type = data.getUInt8();
205
206 Log::info("ClientLobby", "Asynchronous message of type %d",
207 message_type);
208 switch(message_type)
209 {
210 default: break;
211 } // switch
212 } // message
213 else if (event->getType() == EVENT_TYPE_DISCONNECTED)
214 {
215 // This means we left essentially.
216 // We can't delete STKHost from this thread, since the main
217 // thread might still test if STKHost exists and then call
218 // the ProtocolManager, which might already have been deleted.
219 // So only signal that STKHost should exit, which will be tested
220 // from the main thread.
221 STKHost::get()->disconnectAllPeers(false/*timeout_waiting*/);
222 STKHost::get()->setErrorMessage(
223 m_disconnected_msg.at(event->getPeerDisconnectInfo()));
224 STKHost::get()->requestShutdown();
225 } // disconnection
226 return true;
227 } // notifyEventAsynchronous
228
229 //-----------------------------------------------------------------------------
addAllPlayers(Event * event)230 void ClientLobby::addAllPlayers(Event* event)
231 {
232 if (World::getWorld())
233 {
234 Log::warn("ClientLobby", "World already loaded.");
235 return;
236 }
237
238 // In case the user opened a user info dialog
239 GUIEngine::ModalDialog::dismiss();
240 GUIEngine::ScreenKeyboard::dismiss();
241
242 if (!checkDataSize(event, 1))
243 {
244 // If recieved invalid message for players leave now
245 STKHost::get()->disconnectAllPeers(false/*timeout_waiting*/);
246 STKHost::get()->requestShutdown();
247 return;
248 }
249 // Timeout is too slow to synchronize, force it to stop and set current
250 // time
251 if (!STKHost::get()->getNetworkTimerSynchronizer()->isSynchronised())
252 {
253 if (ServerConfig::m_voting_timeout >= 10.0f)
254 {
255 core::stringw msg = _("Bad network connection is detected.");
256 MessageQueue::add(MessageQueue::MT_ERROR, msg);
257 Log::warn("ClientLobby", "Failed to synchronize timer before game "
258 "start, maybe you enter the game too quick? (at least 5 "
259 "seconds are required for synchronization.");
260 }
261 STKHost::get()->getNetworkTimerSynchronizer()->enableForceSetTimer();
262 }
263
264 NetworkString& data = event->data();
265 uint32_t winner_peer_id = data.getUInt32();
266 PeerVote winner_vote(data);
267
268 m_game_setup->setRace(winner_vote);
269 if (!GUIEngine::isNoGraphics())
270 TracksScreen::getInstance()->setResult(winner_peer_id, winner_vote);
271
272 std::shared_ptr<STKPeer> peer = event->getPeerSP();
273 peer->cleanPlayerProfiles();
274 m_server_send_live_load_world = data.getUInt8() == 1;
275
276 bool is_specator = true;
277 std::vector<std::shared_ptr<NetworkPlayerProfile> > players =
278 decodePlayers(data, peer, &is_specator);
279 setSpectator(is_specator);
280
281 uint32_t random_seed = data.getUInt32();
282 ItemManager::updateRandomSeed(random_seed);
283 if (RaceManager::get()->isBattleMode())
284 {
285 int hit_capture_limit = data.getUInt32();
286 float time_limit = data.getFloat();
287 m_game_setup->setHitCaptureTime(hit_capture_limit, time_limit);
288 uint16_t flag_return_timeout = data.getUInt16();
289 RaceManager::get()->setFlagReturnTicks(flag_return_timeout);
290 unsigned flag_deactivated_time = data.getUInt16();
291 RaceManager::get()->setFlagDeactivatedTicks(flag_deactivated_time);
292 }
293 configRemoteKart(players, isSpectator() ? 1 :
294 (int)NetworkConfig::get()->getNetworkPlayers().size());
295 loadWorld();
296 // Disable until render gui during loading is bug free
297 //StateManager::get()->enterGameState();
298
299 if (m_server_send_live_load_world)
300 {
301 World* w = World::getWorld();
302 w->setLiveJoinWorld(true);
303 Camera* cam = Camera::getCamera(0);
304 for (unsigned i = 0; i < w->getNumKarts(); i++)
305 {
306 AbstractKart* k = w->getKart(i);
307 // Change spectating target to first non-eliminated kart
308 if (isSpectator() && cam && !k->isEliminated())
309 {
310 cam->setKart(k);
311 cam = NULL;
312 }
313 // The final joining ticks will be set by server later
314 if (k->getController()->isLocalPlayerController())
315 k->setLiveJoinKart(std::numeric_limits<int>::max());
316 else
317 k->getNode()->setVisible(false);
318 }
319 }
320
321 // Switch to assign mode in case a player hasn't chosen any karts
322 input_manager->getDeviceManager()->setAssignMode(ASSIGN);
323 } // addAllPlayers
324
325 //-----------------------------------------------------------------------------
326 /* Get list of players from server and see if we are spectating it. */
327 std::vector<std::shared_ptr<NetworkPlayerProfile> >
decodePlayers(const BareNetworkString & data,std::shared_ptr<STKPeer> peer,bool * is_spectator) const328 ClientLobby::decodePlayers(const BareNetworkString& data,
329 std::shared_ptr<STKPeer> peer,
330 bool* is_spectator) const
331 {
332 std::vector<std::shared_ptr<NetworkPlayerProfile> > players;
333 unsigned player_count = data.getUInt8();
334 for (unsigned i = 0; i < player_count; i++)
335 {
336 core::stringw player_name;
337 data.decodeStringW(&player_name);
338 uint32_t host_id = data.getUInt32();
339 float kart_color = data.getFloat();
340 uint32_t online_id = data.getUInt32();
341 HandicapLevel handicap = (HandicapLevel)data.getUInt8();
342 uint8_t local_id = data.getUInt8();
343 KartTeam team = (KartTeam)data.getUInt8();
344 std::string country_code;
345 data.decodeString(&country_code);
346 if (is_spectator && host_id == STKHost::get()->getMyHostId())
347 *is_spectator = false;
348 auto player = std::make_shared<NetworkPlayerProfile>(peer, player_name,
349 host_id, kart_color, online_id, handicap, local_id, team, country_code);
350 std::string kart_name;
351 data.decodeString(&kart_name);
352 player->setKartName(kart_name);
353 players.push_back(player);
354 }
355 return players;
356 } // decodePlayers
357
358 //-----------------------------------------------------------------------------
update(int ticks)359 void ClientLobby::update(int ticks)
360 {
361 switch (m_state.load())
362 {
363 case LINKED:
364 {
365 NetworkConfig::get()->clearServerCapabilities();
366 std::string ua = StringUtils::getUserAgentString();
367 if (NetworkConfig::get()->isNetworkAIInstance())
368 ua = "AI";
369 NetworkString* ns = getNetworkString();
370 ns->addUInt8(LE_CONNECTION_REQUESTED)
371 .addUInt32(ServerConfig::m_server_version).encodeString(ua)
372 .addUInt16((uint16_t)stk_config->m_network_capabilities.size());
373 for (const std::string& cap : stk_config->m_network_capabilities)
374 ns->encodeString(cap);
375
376 getKartsTracksNetworkString(ns);
377 assert(!NetworkConfig::get()->isAddingNetworkPlayers());
378 const uint8_t player_count =
379 (uint8_t)NetworkConfig::get()->getNetworkPlayers().size();
380 ns->addUInt8(player_count);
381
382 bool encryption = false;
383 // Make sure there is a player before calling getCurrentOnlineId
384 PlayerManager::get()->enforceCurrentPlayer();
385 uint32_t id = PlayerManager::getCurrentOnlineId();
386 bool lan_ai = !m_server->supportsEncryption() &&
387 NetworkConfig::get()->isNetworkAIInstance();
388 if (lan_ai)
389 id = 0;
390 BareNetworkString* rest = new BareNetworkString();
391 if (m_server->supportsEncryption() && id != 0)
392 {
393 ns->addUInt32(id);
394 encryption = true;
395 }
396 else
397 {
398 ns->addUInt32(id).addUInt32(0);
399 if (id != 0)
400 {
401 ns->encodeString(
402 PlayerManager::getCurrentOnlineProfile()->getUserName());
403 }
404 }
405
406 rest->encodeString(ServerConfig::m_private_server_password)
407 .addUInt8(player_count);
408 for (unsigned i = 0;
409 i < NetworkConfig::get()->getNetworkPlayers().size(); i++)
410 {
411 auto& p = NetworkConfig::get()->getNetworkPlayers()[i];
412 PlayerProfile* player = std::get<1>(p);
413 core::stringw name = player->getName();
414 if (NetworkConfig::get()->isNetworkAIInstance())
415 {
416 // I18N: Shown in lobby to indicate it's a bot in LAN game
417 #ifdef SERVER_ONLY
418 name = L"Bot";
419 #else
420 name = _("Bot");
421 #endif
422 if (i > 0)
423 {
424 name += core::stringw(" ") + StringUtils::toWString(i);
425 }
426 }
427 rest->encodeString(name).
428 addFloat(player->getDefaultKartColor());
429 // Per-player handicap
430 rest->addUInt8(std::get<2>(p));
431 }
432
433 finalizeConnectionRequest(ns, rest, encryption);
434 m_state.store(REQUESTING_CONNECTION);
435 }
436 break;
437 case RACE_FINISHED:
438 if (!RaceEventManager::get()->protocolStopped() ||
439 !GameProtocol::emptyInstance())
440 return;
441 if (!m_received_server_result)
442 {
443 m_received_server_result = true;
444 m_auto_back_to_lobby_time = StkTime::getMonoTimeMs() + 5000;
445 // In case someone opened paused race dialog or menu in network game
446 GUIEngine::ModalDialog::dismiss();
447 GUIEngine::ScreenKeyboard::dismiss();
448 if (StateManager::get()->getGameState() == GUIEngine::INGAME_MENU)
449 StateManager::get()->enterGameState();
450 World::getWorld()->enterRaceOverState();
451 }
452 if (NetworkConfig::get()->isAutoConnect() &&
453 StkTime::getMonoTimeMs() > m_auto_back_to_lobby_time)
454 {
455 m_auto_back_to_lobby_time = std::numeric_limits<uint64_t>::max();
456 doneWithResults();
457 }
458 break;
459 case DONE:
460 m_state.store(EXITING);
461 requestTerminate();
462 break;
463 case REQUESTING_CONNECTION:
464 case CONNECTED:
465 if (m_start_live_game_time != std::numeric_limits<uint64_t>::max() &&
466 STKHost::get()->getNetworkTimer() >= m_start_live_game_time)
467 {
468 finishLiveJoin();
469 }
470 if (NetworkConfig::get()->isAutoConnect() && !m_auto_started)
471 {
472 // Send a message to the server to start
473 m_auto_started = true;
474 NetworkString start(PROTOCOL_LOBBY_ROOM);
475 start.addUInt8(LobbyProtocol::LE_REQUEST_BEGIN);
476 STKHost::get()->sendToServer(&start, true);
477 }
478 case SELECTING_ASSETS:
479 case RACING:
480 case EXITING:
481 case NONE:
482 break;
483 }
484 } // update
485
486 //-----------------------------------------------------------------------------
finalizeConnectionRequest(NetworkString * header,BareNetworkString * rest,bool encrypt)487 void ClientLobby::finalizeConnectionRequest(NetworkString* header,
488 BareNetworkString* rest,
489 bool encrypt)
490 {
491 if (encrypt)
492 {
493 auto crypto = Crypto::getClientCrypto();
494 Crypto::resetClientAES();
495 BareNetworkString* result = new BareNetworkString();
496 if (!crypto->encryptConnectionRequest(*rest))
497 {
498 // Failed
499 result->addUInt32(0);
500 *result += BareNetworkString(rest->getData(), rest->getTotalSize());
501 encrypt = false;
502 }
503 else
504 {
505 Log::info("ClientLobby", "Server will validate this online player.");
506 result->addUInt32(rest->getTotalSize());
507 *result += BareNetworkString(rest->getData(), rest->getTotalSize());
508 }
509 delete rest;
510 *header += *result;
511 delete result;
512 sendToServer(header);
513 delete header;
514 if (encrypt)
515 {
516 STKHost::get()->getServerPeerForClient()
517 ->setCrypto(std::move(crypto));
518 }
519 }
520 else
521 {
522 *header += *rest;
523 delete rest;
524 sendToServer(header);
525 delete header;
526 }
527 } // finalizeConnectionRequest
528
529 //-----------------------------------------------------------------------------
receivePlayerVote(Event * event)530 void ClientLobby::receivePlayerVote(Event* event)
531 {
532 if (!checkDataSize(event, 4)) return;
533 // Get the player name who voted
534 NetworkString& data = event->data();
535 uint32_t host_id = data.getUInt32();
536 PeerVote vote(data);
537 Log::debug("ClientLobby",
538 "Vote from server: host %d, track %s, laps %d, reverse %d.",
539 host_id, vote.m_track_name.c_str(), vote.m_num_laps, vote.m_reverse);
540
541 Track* track = track_manager->getTrack(vote.m_track_name);
542 if (!track)
543 {
544 Log::fatal("ClientLobby", "Missing track %s",
545 vote.m_track_name.c_str());
546 }
547 addVote(host_id, vote);
548 if (!GUIEngine::isNoGraphics())
549 {
550 TracksScreen::getInstance()->addVote(host_id, vote);
551 TracksScreen::getInstance()->updatePlayerVotes();
552 }
553 } // receivePlayerVote
554
555 //-----------------------------------------------------------------------------
556 /*! \brief Called when a new player is disconnected
557 * \param event : Event providing the information.
558 *
559 * Format of the data :
560 * Byte 0
561 * --------------
562 * Size | 1 |
563 * Data | player id *|
564 * --------------
565 */
disconnectedPlayer(Event * event)566 void ClientLobby::disconnectedPlayer(Event* event)
567 {
568 if (!checkDataSize(event, 1)) return;
569
570 NetworkString &data = event->data();
571 unsigned disconnected_player_count = data.getUInt8();
572 uint32_t host_id = data.getUInt32();
573 m_peers_votes.erase(host_id);
574 // If in-game world exists the kart rewinder will know which player
575 // disconnects
576 bool in_game_world = World::getWorld() &&
577 RaceEventManager::get() &&
578 RaceEventManager::get()->isRunning() &&
579 !RaceEventManager::get()->isRaceOver();
580
581 if (!in_game_world)
582 SFXManager::get()->quickSound("appear");
583 for (unsigned i = 0; i < disconnected_player_count; i++)
584 {
585 std::string name;
586 data.decodeString(&name);
587 if (in_game_world)
588 continue;
589 core::stringw player_name = StringUtils::utf8ToWide(name);
590 core::stringw msg = _("%s disconnected.", player_name);
591 // Use the friend icon to avoid an error-like message
592 MessageQueue::add(MessageQueue::MT_FRIEND, msg);
593 }
594 if (!GUIEngine::isNoGraphics())
595 {
596 TracksScreen::getInstance()->removeVote(host_id);
597 TracksScreen::getInstance()->updatePlayerVotes();
598 }
599 } // disconnectedPlayer
600
601 //-----------------------------------------------------------------------------
602 /*! \brief Called when the server accepts the connection.
603 * \param event : Event providing the information.
604 */
connectionAccepted(Event * event)605 void ClientLobby::connectionAccepted(Event* event)
606 {
607 // At least 8 bytes should remain now
608 if (!checkDataSize(event, 8)) return;
609
610 NetworkString &data = event->data();
611 // Accepted
612 // ========
613 Log::info("ClientLobby", "The server accepted the connection.");
614
615 static bool shown_msg = false;
616 if (!shown_msg)
617 {
618 shown_msg = true;
619 // I18N: Message shown in network lobby to tell user that
620 // player name is clickable
621 core::stringw msg = _("Press player name in the list for player"
622 " management and ranking information.");
623 MessageQueue::add(MessageQueue::MT_GENERIC, msg);
624 }
625
626 uint32_t host_id = data.getUInt32();
627 STKHost::get()->setMyHostId(host_id);
628 if (auto sl = LobbyProtocol::getByType<ServerLobby>(PT_CHILD))
629 sl->setClientServerHostId(host_id);
630
631 assert(!NetworkConfig::get()->isAddingNetworkPlayers());
632 uint32_t server_version = data.getUInt32();
633 NetworkConfig::get()->setJoinedServerVersion(server_version);
634 assert(server_version != 0);
635 m_auto_started = false;
636 m_state.store(CONNECTED);
637
638 unsigned list_caps = data.getUInt16();
639 std::set<std::string> caps;
640 for (unsigned i = 0; i < list_caps; i++)
641 {
642 std::string cap;
643 data.decodeString(&cap);
644 caps.insert(cap);
645 }
646 NetworkConfig::get()->setServerCapabilities(caps);
647
648 float auto_start_timer = data.getFloat();
649 int state_frequency_in_server = data.getUInt32();
650 NetworkConfig::get()->setStateFrequency(state_frequency_in_server);
651 if (!GUIEngine::isNoGraphics() &&
652 auto_start_timer != std::numeric_limits<float>::max())
653 NetworkingLobby::getInstance()->setStartingTimerTo(auto_start_timer);
654 m_server_enabled_chat = data.getUInt8() == 1;
655 if (NetworkConfig::get()->getServerCapabilities().find("report_player") !=
656 NetworkConfig::get()->getServerCapabilities().end())
657 m_server_enabled_report_player = data.getUInt8() == 1;
658 } // connectionAccepted
659
660 //-----------------------------------------------------------------------------
handleServerInfo(Event * event)661 void ClientLobby::handleServerInfo(Event* event)
662 {
663 // At least 6 bytes should remain now
664 if (!checkDataSize(event, 6)) return;
665
666 core::stringw str, total_lines;
667 if (!m_first_connect)
668 {
669 total_lines = L"--------------------";
670 total_lines += L"\n";
671 }
672 m_first_connect = false;
673
674 NetworkString &data = event->data();
675 // Add server info
676 uint8_t u_data;
677 data.decodeStringW(&str);
678
679 //I18N: In the networking lobby
680 total_lines += _("Server name: %s", str);
681 total_lines += L"\n";
682
683 u_data = data.getUInt8();
684 const core::stringw& difficulty_name =
685 RaceManager::get()->getDifficultyName((RaceManager::Difficulty)u_data);
686 RaceManager::get()->setDifficulty((RaceManager::Difficulty)u_data);
687 //I18N: In the networking lobby
688 total_lines += _("Difficulty: %s", difficulty_name);
689 total_lines += L"\n";
690
691 unsigned max_player = data.getUInt8();
692 //I18N: In the networking lobby
693 total_lines += _("Max players: %d", (int)max_player);
694 total_lines += L"\n";
695
696 // Reserved for extra spectators
697 u_data = data.getUInt8();
698 u_data = data.getUInt8();
699 auto game_mode = ServerConfig::getLocalGameMode(u_data);
700 RaceManager::get()->setMinorMode(game_mode.first);
701 // We use single mode in network even it's grand prix
702 RaceManager::get()->setMajorMode(RaceManager::MAJOR_MODE_SINGLE);
703
704 //I18N: In the networking lobby
705 core::stringw mode_name = ServerConfig::getModeName(u_data);
706 total_lines += _("Game mode: %s", mode_name);
707 total_lines += L"\n";
708
709 uint8_t extra_server_info = data.getUInt8();
710 bool grand_prix_started = false;
711 m_game_setup->resetExtraServerInfo();
712 switch (extra_server_info)
713 {
714 case 0:
715 break;
716 case 1:
717 {
718 u_data = data.getUInt8();
719 core::stringw tl = _("Time limit");
720 core::stringw gl = _("Goals limit");
721 core::stringw sgt = u_data == 0 ? tl : gl;
722 m_game_setup->setSoccerGoalTarget(u_data != 0);
723 //I18N: In the networking lobby
724 total_lines += _("Soccer game type: %s", sgt);
725 total_lines += L"\n";
726 break;
727 }
728 case 2:
729 {
730 unsigned cur_gp_track = data.getUInt8();
731 grand_prix_started = cur_gp_track != 0;
732 unsigned total_gp_track = data.getUInt8();
733 m_game_setup->setGrandPrixTrack(total_gp_track);
734 total_lines += _("Grand prix progress: %d / %d", cur_gp_track,
735 total_gp_track);
736 total_lines += L"\n";
737 break;
738 }
739 }
740 // Auto start info
741 unsigned min_players = data.getUInt8();
742 float start_timeout = data.getFloat();
743 if (!GUIEngine::isNoGraphics())
744 {
745 NetworkingLobby::getInstance()->initAutoStartTimer(grand_prix_started,
746 min_players, start_timeout, max_player);
747 }
748
749 // MOTD
750 core::stringw motd;
751 data.decodeString16(&motd);
752 if (!motd.empty())
753 total_lines += motd;
754
755 // Remove last newline added, network lobby will add it back later after
756 // removing old server info (with chat)
757 if (total_lines[total_lines.size() - 1] == L'\n')
758 total_lines.erase(total_lines.size() - 1);
759
760 if (!GUIEngine::isNoGraphics())
761 NetworkingLobby::getInstance()->addMoreServerInfo(total_lines);
762
763 bool server_config = data.getUInt8() == 1;
764 if (!GUIEngine::isNoGraphics())
765 NetworkingLobby::getInstance()->toggleServerConfigButton(server_config);
766 m_server_live_joinable = data.getUInt8() == 1;
767 NetworkConfig::get()->setTuxHitboxAddon(m_server_live_joinable);
768 } // handleServerInfo
769
770 //-----------------------------------------------------------------------------
updatePlayerList(Event * event)771 void ClientLobby::updatePlayerList(Event* event)
772 {
773 if (!checkDataSize(event, 1)) return;
774 NetworkString& data = event->data();
775 bool waiting = data.getUInt8() == 1;
776 if (m_waiting_for_game && !waiting)
777 {
778 // The waiting game finished
779 SFXManager::get()->quickSound("wee");
780 }
781
782 m_waiting_for_game = waiting;
783 unsigned player_count = data.getUInt8();
784 core::stringw total_players;
785 m_lobby_players.clear();
786 bool client_server_owner = false;
787 for (unsigned i = 0; i < player_count; i++)
788 {
789 LobbyPlayer lp = {};
790 lp.m_host_id = data.getUInt32();
791 lp.m_online_id = data.getUInt32();
792 uint8_t local_id = data.getUInt8();
793 lp.m_handicap = HANDICAP_NONE;
794 lp.m_local_player_id = local_id;
795 data.decodeStringW(&lp.m_user_name);
796 total_players += lp.m_user_name;
797 uint8_t boolean_combine = data.getUInt8();
798 bool is_peer_waiting_for_game = (boolean_combine & 1) == 1;
799 bool is_spectator = ((boolean_combine >> 1) & 1) == 1;
800 bool is_peer_server_owner = ((boolean_combine >> 2) & 1) == 1;
801 bool ready = ((boolean_combine >> 3) & 1) == 1;
802 bool ai = ((boolean_combine >> 4) & 1) == 1;
803 // icon to be used, see NetworkingLobby::loadedFromFile
804 lp.m_icon_id = is_peer_server_owner ? 0 :
805 lp.m_online_id != 0 /*if online account*/ ? 1 : 2;
806 if (ai)
807 lp.m_icon_id = 6;
808 if (waiting && !is_peer_waiting_for_game)
809 lp.m_icon_id = 3;
810 if (is_spectator)
811 lp.m_icon_id = 5;
812 if (ready)
813 lp.m_icon_id = 4;
814 lp.m_handicap = (HandicapLevel)data.getUInt8();
815 if (lp.m_handicap != HANDICAP_NONE)
816 {
817 lp.m_user_name = _("%s (handicapped)", lp.m_user_name);
818 }
819 lp.m_kart_team = (KartTeam)data.getUInt8();
820 // No handicap for AI peer
821 if (!ai && lp.m_host_id == STKHost::get()->getMyHostId())
822 {
823 if (is_peer_server_owner)
824 client_server_owner = true;
825 auto& local_players = NetworkConfig::get()->getNetworkPlayers();
826 std::get<2>(local_players.at(local_id)) = lp.m_handicap;
827 }
828 data.decodeString(&lp.m_country_code);
829 m_lobby_players.push_back(lp);
830 }
831 STKHost::get()->setAuthorisedToControl(client_server_owner);
832
833 // Notification sound for new player
834 if (!m_total_players.empty() &&
835 total_players.size() > m_total_players.size())
836 SFXManager::get()->quickSound("energy_bar_full");
837 m_total_players = total_players;
838
839 if (!GUIEngine::isNoGraphics())
840 NetworkingLobby::getInstance()->updatePlayers();
841 } // updatePlayerList
842
843 //-----------------------------------------------------------------------------
handleBadTeam()844 void ClientLobby::handleBadTeam()
845 {
846 SFXManager::get()->quickSound("anvil");
847 //I18N: Display when all players are in red or blue team, which the race
848 //will not be allowed to start
849 core::stringw msg = _("All players joined red or blue team.");
850 MessageQueue::add(MessageQueue::MT_ERROR, msg);
851 } // handleBadTeam
852
853 //-----------------------------------------------------------------------------
handleBadConnection()854 void ClientLobby::handleBadConnection()
855 {
856 SFXManager::get()->quickSound("anvil");
857 core::stringw msg = _("Bad network connection is detected.");
858 MessageQueue::add(MessageQueue::MT_ERROR, msg);
859 } // handleBadConnection
860
861 //-----------------------------------------------------------------------------
becomingServerOwner()862 void ClientLobby::becomingServerOwner()
863 {
864 if (STKHost::get()->isClientServer())
865 return;
866
867 SFXManager::get()->quickSound("wee");
868 //I18N: Display when a player is allow to control the server
869 core::stringw msg = _("You are now the owner of server.");
870 MessageQueue::add(MessageQueue::MT_GENERIC, msg);
871 } // becomingServerOwner
872
873 //-----------------------------------------------------------------------------
handleChat(Event * event)874 void ClientLobby::handleChat(Event* event)
875 {
876 if (!UserConfigParams::m_lobby_chat)
877 return;
878 SFXManager::get()->quickSound("plopp");
879 core::stringw message;
880 event->data().decodeString16(&message);
881 Log::info("ClientLobby", "%s", StringUtils::wideToUtf8(message).c_str());
882 if (GUIEngine::isNoGraphics())
883 return;
884 if (message.size() > 0)
885 {
886 if (GUIEngine::getCurrentScreen() == NetworkingLobby::getInstance())
887 NetworkingLobby::getInstance()->addMoreServerInfo(message);
888 else if (UserConfigParams::m_race_chat)
889 MessageQueue::add(MessageQueue::MT_GENERIC, message);
890 }
891 } // handleChat
892
893 //-----------------------------------------------------------------------------
894 /*! \brief Called when the server refuses the connection.
895 * \param event : Event providing the information.
896 *
897 * Format of the data :
898 * Byte 0
899 * ----------------
900 * Size | 1 |
901 * Data | refusal code |
902 * ----------------
903 */
connectionRefused(Event * event)904 void ClientLobby::connectionRefused(Event* event)
905 {
906 if (!checkDataSize(event, 1)) return;
907 const NetworkString &data = event->data();
908 switch ((RejectReason)data.getUInt8()) // the second byte
909 {
910 case RR_BUSY:
911 STKHost::get()->setErrorMessage(
912 _("Connection refused: Server is busy."));
913 break;
914 case RR_BANNED:
915 {
916 core::stringw msg =
917 _("Connection refused: You are banned from the server.");
918 core::stringw reason;
919 data.decodeStringW(&reason);
920 if (!reason.empty())
921 {
922 msg += L"\n";
923 msg += reason;
924 }
925 STKHost::get()->setErrorMessage(msg);
926 break;
927 }
928 case RR_INCORRECT_PASSWORD:
929 m_server->setReconnectWhenQuitLobby(true);
930 STKHost::get()->setErrorMessage(
931 _("Connection refused: Server password is incorrect."));
932 break;
933 case RR_INCOMPATIBLE_DATA:
934 STKHost::get()->setErrorMessage(
935 _("Connection refused: Game data is incompatible."));
936 break;
937 case RR_TOO_MANY_PLAYERS:
938 STKHost::get()->setErrorMessage(
939 _("Connection refused: Server is full."));
940 break;
941 case RR_INVALID_PLAYER:
942 STKHost::get()->setErrorMessage(
943 _("Connection refused: Invalid player connecting."));
944 break;
945 }
946 STKHost::get()->disconnectAllPeers(false/*timeout_waiting*/);
947 STKHost::get()->requestShutdown();
948 } // connectionRefused
949
950 //-----------------------------------------------------------------------------
951
952 /*! \brief Called when the server broadcasts to start the race to all clients.
953 * \param event : Event providing the time the client should start game.
954 */
startGame(Event * event)955 void ClientLobby::startGame(Event* event)
956 {
957 World::getWorld()->setPhase(WorldStatus::SERVER_READY_PHASE);
958 uint64_t start_time = event->data().getUInt64();
959 powerup_manager->setRandomSeed(start_time);
960
961 unsigned check_structure_count = event->data().getUInt8();
962 LinearWorld* lw = dynamic_cast<LinearWorld*>(World::getWorld());
963 if (lw)
964 lw->handleServerCheckStructureCount(check_structure_count);
965
966 NetworkItemManager* nim = dynamic_cast<NetworkItemManager*>
967 (Track::getCurrentTrack()->getItemManager());
968 assert(nim);
969 nim->restoreCompleteState(event->data());
970
971 core::stringw err_msg = _("Failed to start the network game.");
972 // Different stk process thread may have different stk host
973 STKHost* stk_host = STKHost::get();
974 joinStartGameThread();
975 m_start_game_thread = std::thread([start_time, stk_host, this, err_msg]()
976 {
977 const uint64_t cur_time = stk_host->getNetworkTimer();
978 if (!(start_time > cur_time))
979 {
980 Log::error("ClientLobby", "Network timer is too slow to catch "
981 "up, you must have a poor network.");
982 stk_host->setErrorMessage(err_msg);
983 stk_host->requestShutdown();
984 return;
985 }
986 int sleep_time = (int)(start_time - cur_time);
987 //Log::info("ClientLobby", "Start game after %dms", sleep_time);
988 StkTime::sleep(sleep_time);
989 //Log::info("ClientLobby", "Started at %lf", StkTime::getRealTime());
990 m_state.store(RACING);
991 });
992 } // startGame
993
994 //-----------------------------------------------------------------------------
995 /*! \brief Called when the kart selection starts.
996 * \param event : Event providing the information (no additional information
997 * in this case).
998 */
startSelection(Event * event)999 void ClientLobby::startSelection(Event* event)
1000 {
1001 SFXManager::get()->quickSound("wee");
1002 const NetworkString& data = event->data();
1003 startVotingPeriod(data.getFloat());
1004 bool skip_kart_screen = data.getUInt8() == 1;
1005 m_server_auto_game_time = data.getUInt8() == 1;
1006 m_server_enabled_track_voting = data.getUInt8() == 1;
1007 const unsigned kart_num = data.getUInt16();
1008 const unsigned track_num = data.getUInt16();
1009 m_available_karts.clear();
1010 m_available_tracks.clear();
1011 for (unsigned i = 0; i < kart_num; i++)
1012 {
1013 std::string kart;
1014 data.decodeString(&kart);
1015 m_available_karts.insert(kart);
1016 }
1017 for (unsigned i = 0; i < track_num; i++)
1018 {
1019 std::string track;
1020 data.decodeString(&track);
1021 m_available_tracks.insert(track);
1022 }
1023
1024 // In case the user opened a user info dialog
1025 GUIEngine::ModalDialog::dismiss();
1026 GUIEngine::ScreenKeyboard::dismiss();
1027
1028 if (!GUIEngine::isNoGraphics())
1029 {
1030 NetworkKartSelectionScreen* screen =
1031 NetworkKartSelectionScreen::getInstance();
1032 screen->setAvailableKartsFromServer(m_available_karts);
1033 screen->setLiveJoin(false);
1034 }
1035 // In case of auto-connect or continue a grand prix, use random karts
1036 // (or previous kart) from server and go to track selection
1037 if ((NetworkConfig::get()->isAutoConnect() || skip_kart_screen) &&
1038 m_server_enabled_track_voting)
1039 {
1040 input_manager->setMasterPlayerOnly(true);
1041 for (auto& p : NetworkConfig::get()->getNetworkPlayers())
1042 {
1043 StateManager::get()
1044 ->createActivePlayer(std::get<1>(p), std::get<0>(p));
1045 }
1046 input_manager->getDeviceManager()->setAssignMode(ASSIGN);
1047 if (!GUIEngine::isNoGraphics())
1048 {
1049 TracksScreen::getInstance()->setQuitServer();
1050 TracksScreen::getInstance()->setNetworkTracks();
1051 TracksScreen::getInstance()->push();
1052 }
1053 }
1054 else if (!GUIEngine::isNoGraphics())
1055 {
1056 NetworkKartSelectionScreen::getInstance()->push();
1057 }
1058
1059 if (!GUIEngine::isNoGraphics())
1060 {
1061 TracksScreen *ts = TracksScreen::getInstance();
1062 ts->resetVote();
1063 }
1064 m_state.store(SELECTING_ASSETS);
1065 Log::info("ClientLobby", "Selection starts now");
1066 } // startSelection
1067
1068 //-----------------------------------------------------------------------------
1069
1070 /*! \brief Called when all karts have finished the race.
1071 * \param event : Event providing the information.
1072 *
1073 * Format of the data :
1074 * Byte 0 1
1075 * -------------------------------
1076 * Size | 1 | 1 | |
1077 * Data | Kart 1 ID | kart id 2 | ... |
1078 * -------------------------------
1079 */
raceFinished(Event * event)1080 void ClientLobby::raceFinished(Event* event)
1081 {
1082 NetworkString &data = event->data();
1083 Log::info("ClientLobby", "Server notified that the race is finished.");
1084 LinearWorld* lw = dynamic_cast<LinearWorld*>(World::getWorld());
1085 if (m_game_setup->isGrandPrix())
1086 {
1087 int t = data.getUInt32();
1088 core::stringw kart_name;
1089 data.decodeStringW(&kart_name);
1090 lw->setFastestLapTicks(t);
1091 lw->setFastestKartName(kart_name);
1092 RaceManager::get()->configGrandPrixResultFromNetwork(data);
1093 }
1094 else if (RaceManager::get()->modeHasLaps())
1095 {
1096 int t = data.getUInt32();
1097 core::stringw kart_name;
1098 data.decodeStringW(&kart_name);
1099 lw->setFastestLapTicks(t);
1100 lw->setFastestKartName(kart_name);
1101 }
1102
1103 if (lw)
1104 {
1105 // Eliminate all karts which have not finished the race, it can happen
1106 // if the last player leave the game instead of crossing the finish
1107 // line
1108 lw->updateRacePosition();
1109 for (unsigned i = 0; i < lw->getNumKarts(); i++)
1110 {
1111 AbstractKart* k = lw->getKart(i);
1112 if (!k->hasFinishedRace() && !k->isEliminated())
1113 {
1114 core::stringw player_name = k->getController()->getName();
1115 core::stringw msg = _("%s left the game.", player_name);
1116 MessageQueue::add(MessageQueue::MT_FRIEND, msg);
1117 World::getWorld()->eliminateKart(i,
1118 false/*notify_of_elimination*/);
1119 k->finishedRace(World::getWorld()->getTime(),
1120 true/*from_server*/);
1121 }
1122 }
1123 }
1124
1125 m_ranking_changes.clear();
1126 // Ranking changes from server
1127 if (NetworkConfig::get()->getServerCapabilities().find("ranking_changes")
1128 != NetworkConfig::get()->getServerCapabilities().end())
1129 {
1130 bool has_ranking_changes = (data.getUInt8() & 1) != 0;
1131 if (has_ranking_changes)
1132 {
1133 unsigned count = data.getUInt8();
1134 for (unsigned i = 0; i < count; i++)
1135 m_ranking_changes.push_back(data.getFloat());
1136 }
1137 }
1138
1139 // stop race protocols
1140 RaceEventManager::get()->stop();
1141 ProtocolManager::lock()->findAndTerminate(PROTOCOL_GAME_EVENTS);
1142 ProtocolManager::lock()->findAndTerminate(PROTOCOL_CONTROLLER_EVENTS);
1143 m_state.store(RACE_FINISHED);
1144 } // raceFinished
1145
1146 //-----------------------------------------------------------------------------
1147 /** Called when the server informs the clients to exit the race result screen.
1148 * It exits the race, and goes back to the lobby.
1149 */
backToLobby(Event * event)1150 void ClientLobby::backToLobby(Event *event)
1151 {
1152 // In case the user opened a user info dialog
1153 GUIEngine::ModalDialog::dismiss();
1154 GUIEngine::ScreenKeyboard::dismiss();
1155
1156 NetworkConfig::get()->clearActivePlayersForClient();
1157 setup();
1158 m_auto_started = false;
1159 m_state.store(CONNECTED);
1160
1161 if (RaceEventManager::get())
1162 {
1163 RaceEventManager::get()->stop();
1164 ProtocolManager::lock()->findAndTerminate(PROTOCOL_GAME_EVENTS);
1165 }
1166 auto gp = GameProtocol::lock();
1167 if (gp)
1168 {
1169 auto lock = gp->acquireWorldDeletingMutex();
1170 ProtocolManager::lock()->findAndTerminate(PROTOCOL_CONTROLLER_EVENTS);
1171 exitGameState();
1172 }
1173 else
1174 exitGameState();
1175
1176 NetworkString &data = event->data();
1177 core::stringw msg;
1178 MessageQueue::MessageType mt = MessageQueue::MT_ERROR;
1179
1180 switch ((BackLobbyReason)data.getUInt8()) // the second byte
1181 {
1182 case BLR_NO_GAME_FOR_LIVE_JOIN:
1183 // I18N: Error message shown if live join or spectate failed in network
1184 msg = _("The game has ended, you can't live join or spectate anymore.");
1185 break;
1186 case BLR_NO_PLACE_FOR_LIVE_JOIN:
1187 // I18N: Error message shown if live join failed in network
1188 msg = _("No remaining place in the arena - live join disabled.");
1189 break;
1190 case BLR_ONE_PLAYER_IN_RANKED_MATCH:
1191 // I18N: Error message shown if only 1 player remains in network
1192 msg = _("Only 1 player remaining, returning to lobby.");
1193 break;
1194 case BLR_SERVER_ONWER_QUITED_THE_GAME:
1195 // I18N: Error message shown when all players will go back to lobby
1196 // when server owner quited the game
1197 if (!STKHost::get()->isClientServer())
1198 msg = _("Server owner quit the game.");
1199 break;
1200 case BLR_SPECTATING_NEXT_GAME:
1201 // I18N: Status shown to player when he will be spectating the next game
1202 msg = _("You will be spectating the next game.");
1203 mt = MessageQueue::MT_GENERIC;
1204 break;
1205 default:
1206 break;
1207 }
1208 if (!msg.empty())
1209 {
1210 if (mt == MessageQueue::MT_GENERIC)
1211 SFXManager::get()->quickSound("plopp");
1212 else
1213 SFXManager::get()->quickSound("anvil");
1214 MessageQueue::add(mt, msg);
1215 }
1216 } // backToLobby
1217
1218 //-----------------------------------------------------------------------------
1219 /** Callback when the world is loaded. The client will inform the server
1220 * that the players on this host are ready to start the race. It is called by
1221 * the RaceManager after the world is loaded.
1222 */
finishedLoadingWorld()1223 void ClientLobby::finishedLoadingWorld()
1224 {
1225 NetworkString* ns = getNetworkString(1);
1226 ns->setSynchronous(m_server_send_live_load_world);
1227 ns->addUInt8(LE_CLIENT_LOADED_WORLD);
1228 sendToServer(ns, true);
1229 delete ns;
1230 } // finishedLoadingWorld
1231
1232 //-----------------------------------------------------------------------------
liveJoinAcknowledged(Event * event)1233 void ClientLobby::liveJoinAcknowledged(Event* event)
1234 {
1235 World* w = World::getWorld();
1236 if (!w)
1237 return;
1238
1239 const NetworkString& data = event->data();
1240 m_start_live_game_time = data.getUInt64();
1241 powerup_manager->setRandomSeed(m_start_live_game_time);
1242
1243 unsigned check_structure_count = event->data().getUInt8();
1244 LinearWorld* lw = dynamic_cast<LinearWorld*>(World::getWorld());
1245 if (lw)
1246 lw->handleServerCheckStructureCount(check_structure_count);
1247
1248 m_start_live_game_time = data.getUInt64();
1249 m_last_live_join_util_ticks = data.getUInt32();
1250 for (unsigned i = 0; i < w->getNumKarts(); i++)
1251 {
1252 AbstractKart* k = w->getKart(i);
1253 if (k->getController()->isLocalPlayerController())
1254 k->setLiveJoinKart(m_last_live_join_util_ticks);
1255 }
1256
1257 NetworkItemManager* nim = dynamic_cast<NetworkItemManager*>
1258 (Track::getCurrentTrack()->getItemManager());
1259 assert(nim);
1260 nim->restoreCompleteState(data);
1261 w->restoreCompleteState(data);
1262
1263 if (RaceManager::get()->supportsLiveJoining() && data.size() > 0)
1264 {
1265 // Get and update the players list 1 more time in case the was
1266 // player connection or disconnection
1267 std::vector<std::shared_ptr<NetworkPlayerProfile> > players =
1268 decodePlayers(data);
1269 w->resetElimination();
1270 for (unsigned i = 0; i < players.size(); i++)
1271 {
1272 AbstractKart* k = w->getKart(i);
1273 if (k->getController()->isLocalPlayerController())
1274 continue;
1275 k->reset();
1276 // Only need to change non local player karts
1277 RemoteKartInfo& rki = RaceManager::get()->getKartInfo(i);
1278 rki.copyFrom(players[i], players[i]->getLocalPlayerId());
1279 if (rki.isReserved())
1280 {
1281 World::getWorld()->eliminateKart(i,
1282 false/*notify_of_elimination*/);
1283 k->setPosition(
1284 World::getWorld()->getCurrentNumKarts() + 1);
1285 k->finishedRace(World::getWorld()->getTime(),
1286 true/*from_server*/);
1287 }
1288 else
1289 {
1290 // Will be reset once again after live join is finished
1291 addLiveJoiningKart(i, rki, 0/*live_join_util_ticks*/);
1292 k->getNode()->setVisible(false);
1293 }
1294 }
1295 }
1296 } // liveJoinAcknowledged
1297
1298 //-----------------------------------------------------------------------------
finishLiveJoin()1299 void ClientLobby::finishLiveJoin()
1300 {
1301 m_start_live_game_time = std::numeric_limits<uint64_t>::max();
1302 World* w = World::getWorld();
1303 if (!w)
1304 return;
1305 Log::info("ClientLobby", "Live join started at %lf",
1306 StkTime::getRealTime());
1307
1308 w->setLiveJoinWorld(false);
1309 w->endLiveJoinWorld(m_last_live_join_util_ticks);
1310 for (unsigned i = 0; i < w->getNumKarts(); i++)
1311 {
1312 AbstractKart* k = w->getKart(i);
1313 if (!k->getController()->isLocalPlayerController() &&
1314 !k->isEliminated())
1315 k->getNode()->setVisible(true);
1316 }
1317 m_state.store(RACING);
1318 } // finishLiveJoin
1319
1320 //-----------------------------------------------------------------------------
requestKartInfo(uint8_t kart_id)1321 void ClientLobby::requestKartInfo(uint8_t kart_id)
1322 {
1323 NetworkString* ns = getNetworkString(1);
1324 ns->setSynchronous(true);
1325 ns->addUInt8(LE_KART_INFO).addUInt8(kart_id);
1326 sendToServer(ns, true/*reliable*/);
1327 delete ns;
1328 } // requestKartInfo
1329
1330 //-----------------------------------------------------------------------------
handleKartInfo(Event * event)1331 void ClientLobby::handleKartInfo(Event* event)
1332 {
1333 World* w = World::getWorld();
1334 if (!w)
1335 return;
1336
1337 const NetworkString& data = event->data();
1338 int live_join_util_ticks = data.getUInt32();
1339 uint8_t kart_id = data.getUInt8();
1340 core::stringw player_name;
1341 data.decodeStringW(&player_name);
1342 uint32_t host_id = data.getUInt32();
1343 float kart_color = data.getFloat();
1344 uint32_t online_id = data.getUInt32();
1345 HandicapLevel h = (HandicapLevel)data.getUInt8();
1346 uint8_t local_id = data.getUInt8();
1347 std::string kart_name;
1348 data.decodeString(&kart_name);
1349 std::string country_code;
1350 data.decodeString(&country_code);
1351
1352 RemoteKartInfo& rki = RaceManager::get()->getKartInfo(kart_id);
1353 rki.setPlayerName(player_name);
1354 rki.setHostId(host_id);
1355 rki.setDefaultKartColor(kart_color);
1356 rki.setOnlineId(online_id);
1357 rki.setHandicap(h);
1358 rki.setLocalPlayerId(local_id);
1359 rki.setKartName(kart_name);
1360 rki.setCountryCode(country_code);
1361 addLiveJoiningKart(kart_id, rki, live_join_util_ticks);
1362
1363 core::stringw msg;
1364 if (RaceManager::get()->teamEnabled())
1365 {
1366 if (w->getKartTeam(kart_id) == KART_TEAM_RED)
1367 {
1368 // I18N: Show when player join red team of the started game in
1369 // network
1370 msg = _("%s joined the red team.", player_name);
1371 }
1372 else
1373 {
1374 // I18N: Show when player join blue team of the started game in
1375 // network
1376 msg = _("%s joined the blue team.", player_name);
1377 }
1378 }
1379 else
1380 {
1381 // I18N: Show when player join the started game in network
1382 msg = _("%s joined the game.", player_name);
1383 }
1384 SFXManager::get()->quickSound("energy_bar_full");
1385 MessageQueue::add(MessageQueue::MT_FRIEND, msg);
1386 } // handleKartInfo
1387
1388 //-----------------------------------------------------------------------------
startLiveJoinKartSelection()1389 void ClientLobby::startLiveJoinKartSelection()
1390 {
1391 NetworkKartSelectionScreen::getInstance()->setLiveJoin(true);
1392 std::vector<int> all_k =
1393 kart_properties_manager->getKartsInGroup("standard");
1394 std::set<std::string> karts;
1395 for (int kart : all_k)
1396 {
1397 const KartProperties* kp = kart_properties_manager->getKartById(kart);
1398 if (!kp->isAddon())
1399 karts.insert(kp->getIdent());
1400 }
1401 NetworkKartSelectionScreen::getInstance()
1402 ->setAvailableKartsFromServer(karts);
1403 NetworkKartSelectionScreen::getInstance()->push();
1404 } // startLiveJoinKartSelection
1405
1406 // ----------------------------------------------------------------------------
sendChat(irr::core::stringw text,KartTeam team)1407 void ClientLobby::sendChat(irr::core::stringw text, KartTeam team)
1408 {
1409 text = text.trim().removeChars(L"\n\r");
1410 if (text.size() > 0)
1411 {
1412 NetworkString* chat = getNetworkString();
1413 chat->addUInt8(LobbyProtocol::LE_CHAT);
1414
1415 core::stringw name;
1416 PlayerProfile* player = PlayerManager::getCurrentPlayer();
1417 if (PlayerManager::getCurrentOnlineState() ==
1418 PlayerProfile::OS_SIGNED_IN)
1419 name = PlayerManager::getCurrentOnlineProfile()->getUserName();
1420 else
1421 name = player->getName();
1422 chat->encodeString16(name + L": " + text, 1000/*max_len*/);
1423
1424 if (team != KART_TEAM_NONE)
1425 chat->addUInt8(team);
1426
1427 STKHost::get()->sendToServer(chat, true);
1428 delete chat;
1429 }
1430 } // sendChat
1431
1432 // ----------------------------------------------------------------------------
changeSpectateTarget(PlayerAction action,int value,Input::InputType type) const1433 void ClientLobby::changeSpectateTarget(PlayerAction action, int value,
1434 Input::InputType type) const
1435 {
1436 Camera* cam = Camera::getActiveCamera();
1437 if (!cam)
1438 return;
1439
1440 // Only 1 local player will be able to change target, and this will replace
1441 // the end camera with normal
1442 if (cam->getType() != Camera::CM_TYPE_NORMAL)
1443 Camera::changeCamera(0, Camera::CM_TYPE_NORMAL);
1444
1445 // Update if the camera again beacuse when race finished cam will be
1446 // changed above and invalid
1447 cam = Camera::getActiveCamera();
1448 if (!cam)
1449 return;
1450
1451 // Copied from EventHandler::processGUIAction
1452 const bool pressed_down = value > Input::MAX_VALUE * 2 / 3;
1453
1454 if (!pressed_down)
1455 return;
1456
1457 if (action == PA_PAUSE_RACE)
1458 {
1459 StateManager::get()->escapePressed();
1460 return;
1461 }
1462 if (action == PA_LOOK_BACK)
1463 {
1464 if (cam->getMode() == Camera::CM_NORMAL)
1465 cam->setMode(Camera::CM_REVERSE);
1466 else
1467 cam->setMode(Camera::CM_NORMAL);
1468 return;
1469 }
1470 if (action == PA_ACCEL)
1471 {
1472 cam->setNextSpectatorMode();
1473 return;
1474 }
1475
1476 WorldWithRank* wwr = dynamic_cast<WorldWithRank*>(World::getWorld());
1477 if (!wwr)
1478 return;
1479 std::vector<AbstractKart*> karts;
1480 for (unsigned i = 0; i < wwr->getNumKarts(); i++)
1481 karts.push_back(wwr->getKartAtDrawingPosition(i + 1));
1482
1483 const int num_karts = (int)karts.size();
1484 int current_idx = -1;
1485 if (cam->getKart())
1486 {
1487 auto it = std::find(karts.begin(), karts.end(), cam->getKart());
1488 if (it != karts.end())
1489 current_idx = (int)std::distance(karts.begin(), it);
1490 }
1491 if (current_idx < 0 || current_idx >= num_karts)
1492 return;
1493
1494 bool up = false;
1495 if (action == PA_STEER_LEFT)
1496 up = false;
1497 else if (action == PA_STEER_RIGHT)
1498 up = true;
1499 else
1500 return;
1501 for (int i = 0; i < num_karts; i++)
1502 {
1503 current_idx = up ? current_idx + 1 : current_idx - 1;
1504 // Handle looping
1505 if (current_idx == -1)
1506 current_idx = num_karts - 1;
1507 else if (current_idx == num_karts)
1508 current_idx = 0;
1509
1510 if (!karts[current_idx]->isEliminated())
1511 {
1512 cam->setKart(karts[current_idx]);
1513 break;
1514 }
1515 }
1516 } // changeSpectateTarget
1517
1518 // ----------------------------------------------------------------------------
addSpectateHelperMessage() const1519 void ClientLobby::addSpectateHelperMessage() const
1520 {
1521 auto& local_players = NetworkConfig::get()->getNetworkPlayers();
1522 if (local_players.empty())
1523 return;
1524 InputDevice* id = std::get<0>(local_players[0]);
1525 if (!id)
1526 return;
1527 DeviceConfig* dc = id->getConfiguration();
1528 if (!dc)
1529 return;
1530 core::stringw left = dc->getBindingAsString(PA_STEER_LEFT);
1531 core::stringw right = dc->getBindingAsString(PA_STEER_RIGHT);
1532 core::stringw back = dc->getBindingAsString(PA_LOOK_BACK);
1533 core::stringw accel = dc->getBindingAsString(PA_ACCEL);
1534
1535 // I18N: Message shown in game to tell the player it's possible to change
1536 // the camera target in spectate mode of network
1537 core::stringw msg = _("Press <%s> or <%s> to change the targeted player, "
1538 "<%s> or <%s> for the camera position.", left, right, back, accel);
1539 MessageQueue::add(MessageQueue::MT_GENERIC, msg);
1540 } // addSpectateHelperMessage
1541
1542 // ----------------------------------------------------------------------------
reportSuccess(Event * event)1543 void ClientLobby::reportSuccess(Event* event)
1544 {
1545 bool succeeded = false;
1546 core::stringw reporting_name;
1547 succeeded = event->data().getUInt8() == 1;
1548 if (succeeded)
1549 event->data().decodeStringW(&reporting_name);
1550 if (succeeded && !reporting_name.empty())
1551 {
1552 // I18N: Tell player he has successfully report this named player
1553 core::stringw msg = _("Successfully reported %s.", reporting_name);
1554 MessageQueue::add(MessageQueue::MT_GENERIC, msg);
1555 }
1556 } // reportSuccess
1557
1558 // ----------------------------------------------------------------------------
handleClientCommand(const std::string & cmd)1559 void ClientLobby::handleClientCommand(const std::string& cmd)
1560 {
1561 #ifndef SERVER_ONLY
1562 auto argv = StringUtils::split(cmd, ' ');
1563 if (argv.size() == 0)
1564 return;
1565 if (argv[0] == "installaddon" && argv.size() == 2)
1566 AddonsPack::install(argv[1]);
1567 else if (argv[0] == "uninstalladdon" && argv.size() == 2)
1568 AddonsPack::uninstall(argv[1]);
1569 else if (argv[0] == "music" && argv.size() == 2)
1570 {
1571 int vol = atoi(argv[1].c_str());
1572 if (vol > 10)
1573 vol = 10;
1574 else if (vol < 0)
1575 vol = 0;
1576 if (vol == 0 && UserConfigParams::m_music)
1577 {
1578 UserConfigParams::m_music = false;
1579 music_manager->stopMusic();
1580 }
1581 else if (vol != 0 && !UserConfigParams::m_music)
1582 {
1583 UserConfigParams::m_music = true;
1584 music_manager->startMusic();
1585 music_manager->setMasterMusicVolume((float)vol / 10);
1586 }
1587 else
1588 music_manager->setMasterMusicVolume((float)vol / 10);
1589 }
1590 else if (argv[0] == "liststkaddon")
1591 {
1592 if (argv.size() > 3)
1593 {
1594 NetworkingLobby::getInstance()->addMoreServerInfo(
1595 L"Usage: /liststkaddon [option][addon prefix letter(s) to find]");
1596 NetworkingLobby::getInstance()->addMoreServerInfo(
1597 L"available options: -kart, -track, -arena.");
1598 }
1599 else
1600 {
1601 std::string msg = "";
1602 for (unsigned i = 0; i < addons_manager->getNumAddons(); i++)
1603 {
1604 // addon_ (6 letters)
1605 const Addon& addon = addons_manager->getAddon(i);
1606 const std::string& addon_id = addon.getId();
1607 if (addon.testStatus(Addon::AS_APPROVED))
1608 {
1609 std::string type = "";
1610 std::string text = "";
1611 if(argv.size() > 1)
1612 {
1613 if(argv[1].compare("-track") == 0 ||
1614 argv[1].compare("-arena") == 0 ||
1615 argv[1].compare("-kart" ) == 0)
1616 type = argv[1].substr(1);
1617 if((argv.size() == 2 && type.empty()) || argv.size() == 3)
1618 text = argv[argv.size()-1];
1619 }
1620
1621 if(!type.empty() && addon.getType()!=type)
1622 continue; // only show given type
1623 if(!text.empty() && addon_id.find(text, 6) == std::string::npos)
1624 continue; // only show matched
1625
1626 msg += addon_id.substr(6);
1627 msg += ", ";
1628 }
1629 }
1630 if (msg.empty())
1631 NetworkingLobby::getInstance()->addMoreServerInfo(L"Addon not found");
1632 else
1633 {
1634 msg = msg.substr(0, msg.size() - 2);
1635 NetworkingLobby::getInstance()->addMoreServerInfo(
1636 StringUtils::utf8ToWide
1637 (std::string("STK addon: ") + msg));
1638 }
1639 }
1640 }
1641 else if (argv[0] == "listlocaladdon")
1642 {
1643 if (argv.size() > 3)
1644 {
1645 NetworkingLobby::getInstance()->addMoreServerInfo(
1646 L"Usage: /listlocaladdon [option][addon prefix letter(s) to find]");
1647 NetworkingLobby::getInstance()->addMoreServerInfo(
1648 L"available options: -kart, -track/-arena, -skin.");
1649 }
1650 else
1651 {
1652 std::string type = "";
1653 std::string text = "";
1654 if(argv.size() > 1)
1655 {
1656 if(argv[1].compare("-track") == 0 ||
1657 argv[1].compare("-arena") == 0 ||
1658 argv[1].compare("-kart" ) == 0 ||
1659 argv[1].compare("-skin" ) == 0)
1660 type = argv[1].substr(1);
1661 if((argv.size() == 2 && type.empty()) || argv.size() == 3)
1662 text = argv[argv.size()-1];
1663 }
1664
1665 std::set<std::string> total_addons;
1666 if(type.empty() || // not specify addon type
1667 (!type.empty() && type.compare("kart") == 0)) // list kart addon
1668 {
1669 for (unsigned i = 0;
1670 i < kart_properties_manager->getNumberOfKarts(); i++)
1671 {
1672 const KartProperties* kp =
1673 kart_properties_manager->getKartById(i);
1674 if (kp->isAddon())
1675 total_addons.insert(kp->getIdent());
1676 }
1677 }
1678 if(type.empty() || // not specify addon type
1679 (!type.empty() && (type.compare("track") == 0 || type.compare("arena") == 0)))
1680 {
1681 for (unsigned i = 0; i < track_manager->getNumberOfTracks(); i++)
1682 {
1683 const Track* track = track_manager->getTrack(i);
1684 if (track->isAddon())
1685 total_addons.insert(track->getIdent());
1686 }
1687 }
1688 if(type.empty() || // not specify addon type
1689 (!type.empty() && type.compare("skin") == 0))
1690 {
1691 std::set<std::string> addon_skin_files;
1692 std::string skin_folder = file_manager->getAddonsFile("skins/");
1693 file_manager->listFiles(addon_skin_files/*out*/, skin_folder,
1694 false/*make full path*/);
1695 for (auto& skin : addon_skin_files)
1696 {
1697 if (skin == "." || skin == "..")
1698 continue;
1699 std::string stkskin = skin_folder + skin + "/stkskin.xml";
1700 if (file_manager->fileExists(stkskin))
1701 total_addons.insert(Addon::createAddonId(skin));
1702 }
1703 }
1704 std::string msg = "";
1705 for (auto& addon : total_addons)
1706 {
1707 // addon_ (6 letters)
1708 if (!text.empty() && addon.find(text, 6) == std::string::npos)
1709 continue;
1710
1711 msg += addon.substr(6);
1712 msg += ", ";
1713 }
1714 if (msg.empty())
1715 NetworkingLobby::getInstance()->addMoreServerInfo(L"Addon not found");
1716 else
1717 {
1718 msg = msg.substr(0, msg.size() - 2);
1719 NetworkingLobby::getInstance()->addMoreServerInfo(
1720 StringUtils::utf8ToWide(
1721 std::string("Local addon: ") + msg));
1722 }
1723 }
1724 }
1725 else
1726 {
1727 // Send for server command
1728 NetworkString* cmd_ns = getNetworkString(1);
1729 const std::string& language = UserConfigParams::m_language;
1730 cmd_ns->addUInt8(LE_COMMAND).encodeString(language).encodeString(cmd);
1731 sendToServer(cmd_ns, /*reliable*/true);
1732 delete cmd_ns;
1733 }
1734 #endif
1735 } // handleClientCommand
1736
1737 // ----------------------------------------------------------------------------
getKartsTracksNetworkString(BareNetworkString * ns)1738 void ClientLobby::getKartsTracksNetworkString(BareNetworkString* ns)
1739 {
1740 auto all_k = kart_properties_manager->getAllAvailableKarts();
1741 auto all_t = track_manager->getAllTrackIdentifiers();
1742 if (all_k.size() >= 65536)
1743 all_k.resize(65535);
1744 if (all_t.size() >= 65536)
1745 all_t.resize(65535);
1746 ns->addUInt16((uint16_t)all_k.size()).addUInt16((uint16_t)all_t.size());
1747 for (const std::string& kart : all_k)
1748 {
1749 ns->encodeString(kart);
1750 }
1751 for (const std::string& track : all_t)
1752 {
1753 ns->encodeString(track);
1754 }
1755 } // getKartsTracksNetworkString
1756
1757 // ----------------------------------------------------------------------------
updateAssetsToServer()1758 void ClientLobby::updateAssetsToServer()
1759 {
1760 NetworkString* ns = getNetworkString(1);
1761 ns->addUInt8(LE_ASSETS_UPDATE);
1762 getKartsTracksNetworkString(ns);
1763 sendToServer(ns, /*reliable*/true);
1764 delete ns;
1765 } // updateAssetsToServer
1766