@@ -1025,33 +1025,32 @@ void CMenus::Render()
10251025 m_CommunityIcons.Update ();
10261026 }
10271027
1028- if (ServerBrowser ()->DDNetInfoAvailable ())
1028+ // Initially add DDNet as favorite community and select its tab.
1029+ // This must be delayed until the DDNet info is available.
1030+ if (m_CreateDefaultFavoriteCommunities &&
1031+ ServerBrowser ()->DDNetInfoAvailable ())
10291032 {
1030- // Initially add DDNet as favorite community and select its tab.
1031- // This must be delayed until the DDNet info is available.
1032- if (m_CreateDefaultFavoriteCommunities)
1033+ m_CreateDefaultFavoriteCommunities = false ;
1034+ if (ServerBrowser ()->Community (IServerBrowser::COMMUNITY_DDNET) != nullptr )
10331035 {
1034- m_CreateDefaultFavoriteCommunities = false ;
1035- if (ServerBrowser ()->Community (IServerBrowser::COMMUNITY_DDNET) != nullptr )
1036- {
1037- ServerBrowser ()->FavoriteCommunitiesFilter ().Clear ();
1038- ServerBrowser ()->FavoriteCommunitiesFilter ().Add (IServerBrowser::COMMUNITY_DDNET);
1039- SetMenuPage (PAGE_FAVORITE_COMMUNITY_1);
1040- ServerBrowser ()->Refresh (IServerBrowser::TYPE_FAVORITE_COMMUNITY_1);
1041- }
1036+ ServerBrowser ()->FavoriteCommunitiesFilter ().Clear ();
1037+ ServerBrowser ()->FavoriteCommunitiesFilter ().Add (IServerBrowser::COMMUNITY_DDNET);
1038+ SetMenuPage (PAGE_FAVORITE_COMMUNITY_1);
1039+ ServerBrowser ()->Refresh (IServerBrowser::TYPE_FAVORITE_COMMUNITY_1);
10421040 }
1043-
1044- if (m_JoinTutorial && m_Popup == POPUP_NONE && !ServerBrowser ()->IsGettingServerlist ())
1041+ }
1042+ if (m_JoinTutorial.m_Queued && m_Popup == POPUP_NONE)
1043+ {
1044+ const char *pAddr = ServerBrowser ()->GetTutorialServer ();
1045+ if (pAddr)
10451046 {
1046- m_JoinTutorial = false ;
1047- // This is only reached on first launch, when the DDNet community tab has been created and
1048- // activated by default, so the server info for the tutorial server should be available.
1049- const char *pAddr = ServerBrowser ()->GetTutorialServer ();
1050- if (pAddr)
1051- {
1052- Client ()->Connect (pAddr);
1053- }
1047+ Client ()->Connect (pAddr);
1048+ }
1049+ else
1050+ {
1051+ m_Popup = POPUP_JOIN_TUTORIAL;
10541052 }
1053+ m_JoinTutorial.m_Queued = false ;
10551054 }
10561055
10571056 // Determine the client state once before rendering because it can change
@@ -1284,6 +1283,10 @@ void CMenus::RenderPopupFullscreen(CUIRect Screen)
12841283 pButtonText = Localize (" Ok" );
12851284 TopAlign = true ;
12861285 }
1286+ else if (m_Popup == POPUP_JOIN_TUTORIAL)
1287+ {
1288+ pTitle = Localize (" Joining Tutorial server" );
1289+ }
12871290 else if (m_Popup == POPUP_POINTS)
12881291 {
12891292 pTitle = Localize (" Existing Player" );
@@ -1338,6 +1341,7 @@ void CMenus::RenderPopupFullscreen(CUIRect Screen)
13381341 }
13391342
13401343 // Extra text (optional)
1344+ if (m_Popup != POPUP_JOIN_TUTORIAL)
13411345 {
13421346 CUIRect ExtraText;
13431347 Box.HSplitTop (24 .0f , &ExtraText, &Box);
@@ -1761,15 +1765,14 @@ void CMenus::RenderPopupFullscreen(CUIRect Screen)
17611765 static CButtonContainer s_JoinTutorialButton;
17621766 if (DoButton_Menu (&s_JoinTutorialButton, Localize (" Join Tutorial Server" ), 0 , &Join) || Ui ()->ConsumeHotkey (CUi::HOTKEY_ENTER))
17631767 {
1764- m_JoinTutorial = true ;
17651768 Client ()->RequestDDNetInfo ();
17661769 m_Popup = g_Config.m_BrIndicateFinished ? POPUP_POINTS : POPUP_NONE;
1770+ JoinTutorial ();
17671771 }
17681772
17691773 static CButtonContainer s_SkipTutorialButton;
17701774 if (DoButton_Menu (&s_SkipTutorialButton, Localize (" Skip Tutorial" ), 0 , &Skip) || Ui ()->ConsumeHotkey (CUi::HOTKEY_ESCAPE))
17711775 {
1772- m_JoinTutorial = false ;
17731776 Client ()->RequestDDNetInfo ();
17741777 m_Popup = g_Config.m_BrIndicateFinished ? POPUP_POINTS : POPUP_NONE;
17751778 }
@@ -1797,6 +1800,193 @@ void CMenus::RenderPopupFullscreen(CUIRect Screen)
17971800 s_PlayerNameInput.SetEmptyText (Client ()->PlayerName ());
17981801 Ui ()->DoEditBox (&s_PlayerNameInput, &TextBox, 12 .0f );
17991802 }
1803+ else if (m_Popup == POPUP_JOIN_TUTORIAL)
1804+ {
1805+ CUIRect ButtonBar, StatusLabel, ProgressLabel, ProgressIndicator;
1806+ Box.HSplitBottom (20 .0f , &Box, nullptr );
1807+ Box.HSplitBottom (24 .0f , &Box, &ButtonBar);
1808+ ButtonBar.VMargin (120 .0f , &ButtonBar);
1809+ Box.HSplitBottom (20 .0f , &StatusLabel, nullptr );
1810+ StatusLabel.VMargin (20 .0f , &StatusLabel);
1811+ StatusLabel.HSplitMid (&StatusLabel, &ProgressLabel);
1812+ ProgressLabel.VSplitLeft (50 .0f , &ProgressIndicator, &ProgressLabel);
1813+
1814+ if (m_JoinTutorial.m_Status == CJoinTutorial::EStatus::REFRESHING)
1815+ {
1816+ if (ServerBrowser ()->IsGettingServerlist () ||
1817+ Client ()->InfoState () == IClient::EInfoState::LOADING)
1818+ {
1819+ // Still refreshing
1820+ }
1821+ else if (ServerBrowser ()->IsServerlistError () ||
1822+ Client ()->InfoState () == IClient::EInfoState::ERROR)
1823+ {
1824+ m_JoinTutorial.m_Status = CJoinTutorial::EStatus::SERVER_LIST_ERROR;
1825+ }
1826+ else
1827+ {
1828+ const char *pAddr = ServerBrowser ()->GetTutorialServer ();
1829+ if (pAddr)
1830+ {
1831+ Client ()->Connect (pAddr);
1832+ }
1833+ else
1834+ {
1835+ m_JoinTutorial.m_Status = CJoinTutorial::EStatus::NO_TUTORIAL_AVAILABLE;
1836+ }
1837+ }
1838+ }
1839+
1840+ const char *pStatusLabel = nullptr ;
1841+ switch (m_JoinTutorial.m_Status )
1842+ {
1843+ case CJoinTutorial::EStatus::REFRESHING:
1844+ pStatusLabel = Localize (" Getting server list from master server" );
1845+ break ;
1846+ case CJoinTutorial::EStatus::SERVER_LIST_ERROR:
1847+ pStatusLabel = Localize (" Could not get server list from master server" );
1848+ break ;
1849+ case CJoinTutorial::EStatus::NO_TUTORIAL_AVAILABLE:
1850+ pStatusLabel = Localize (" There are no Tutorial servers available" );
1851+ break ;
1852+ }
1853+ if (pStatusLabel != nullptr )
1854+ {
1855+ Ui ()->DoLabel (&StatusLabel, pStatusLabel, 20 .0f , TEXTALIGN_ML);
1856+ }
1857+
1858+ const char *pProgressLabel = nullptr ;
1859+ bool ProgressDeterminate = true ;
1860+ const float LastStateChangeSeconds = std::chrono::duration_cast<std::chrono::duration<float >>(time_get_nanoseconds () - m_JoinTutorial.m_StateChange ).count ();
1861+ constexpr float RefreshDelay = 5 .0f ;
1862+
1863+ if (m_JoinTutorial.m_Status == CJoinTutorial::EStatus::REFRESHING)
1864+ {
1865+ pProgressLabel = Localize (" Please wait…" );
1866+ ProgressDeterminate = false ;
1867+ }
1868+ else if (!m_JoinTutorial.m_TryRefresh )
1869+ {
1870+ if (!m_JoinTutorial.m_TriedRefresh )
1871+ {
1872+ m_JoinTutorial.m_TryRefresh = true ;
1873+ m_JoinTutorial.m_StateChange = time_get_nanoseconds ();
1874+ }
1875+ else if (m_JoinTutorial.m_LocalServerState == CJoinTutorial::ELocalServerState::NOT_TRIED)
1876+ {
1877+ m_JoinTutorial.m_LocalServerState = CJoinTutorial::ELocalServerState::TRY;
1878+ m_JoinTutorial.m_StateChange = time_get_nanoseconds ();
1879+ }
1880+ }
1881+
1882+ if (m_JoinTutorial.m_TryRefresh )
1883+ {
1884+ if (LastStateChangeSeconds >= RefreshDelay)
1885+ {
1886+ // Activate internet tab before joining tutorial to make sure the server info
1887+ // for the tutorial servers is available.
1888+ GameClient ()->m_Menus .SetMenuPage (CMenus::PAGE_INTERNET);
1889+ GameClient ()->m_Menus .RefreshBrowserTab (true );
1890+ m_JoinTutorial.m_Status = CJoinTutorial::EStatus::REFRESHING;
1891+ m_JoinTutorial.m_TryRefresh = false ;
1892+ m_JoinTutorial.m_TriedRefresh = true ;
1893+ m_JoinTutorial.m_StateChange = time_get_nanoseconds ();
1894+ }
1895+ else
1896+ {
1897+ pProgressLabel = Localize (" Retrying…" );
1898+ }
1899+ }
1900+
1901+ const auto &&ShowFinalErrorMessage = [&]() {
1902+ PopupMessage (Localize (" Error joining Tutorial server" ), Localize (" Could not find a Tutorial server. Check your internet connection." ), Localize (" Ok" ));
1903+ };
1904+ const auto &&RunServer = [&]() {
1905+ char aMotd[256 ];
1906+ str_copy (aMotd, " sv_motd \" " );
1907+ char *pDst = aMotd + str_length (aMotd);
1908+ str_escape (&pDst, Localize (" You're playing on a local server because no online Tutorial server could be found.\n\n Your record will only be saved locally." ), aMotd + sizeof (aMotd) - 1 );
1909+ str_append (aMotd, " \" " );
1910+ if (GameClient ()->m_LocalServer .RunServer ({" sv_register 0" , " sv_map Tutorial" , aMotd}))
1911+ {
1912+ m_JoinTutorial.m_LocalServerState = CJoinTutorial::ELocalServerState::WAITING_START;
1913+ m_JoinTutorial.m_StateChange = time_get_nanoseconds ();
1914+ }
1915+ else
1916+ {
1917+ ShowFinalErrorMessage ();
1918+ }
1919+ };
1920+ if (m_JoinTutorial.m_LocalServerState == CJoinTutorial::ELocalServerState::TRY)
1921+ {
1922+ if (LastStateChangeSeconds >= RefreshDelay)
1923+ {
1924+ if (GameClient ()->m_LocalServer .IsServerRunning ())
1925+ {
1926+ GameClient ()->m_LocalServer .KillServer ();
1927+ m_JoinTutorial.m_LocalServerState = CJoinTutorial::ELocalServerState::WAITING_STOP;
1928+ m_JoinTutorial.m_StateChange = time_get_nanoseconds ();
1929+ }
1930+ else
1931+ {
1932+ RunServer ();
1933+ }
1934+ }
1935+ else
1936+ {
1937+ pProgressLabel = Localize (" Could not find online Tutorial server.\n Starting and connecting to local server…" );
1938+ }
1939+ }
1940+ else if (m_JoinTutorial.m_LocalServerState == CJoinTutorial::ELocalServerState::WAITING_STOP)
1941+ {
1942+ if (LastStateChangeSeconds >= 5 .0f )
1943+ {
1944+ ShowFinalErrorMessage ();
1945+ }
1946+ else
1947+ {
1948+ if (!GameClient ()->m_LocalServer .IsServerRunning ())
1949+ {
1950+ RunServer ();
1951+ }
1952+
1953+ pProgressLabel = Localize (" Waiting for local server to stop…" );
1954+ ProgressDeterminate = false ;
1955+ }
1956+ }
1957+ else if (m_JoinTutorial.m_LocalServerState == CJoinTutorial::ELocalServerState::WAITING_START)
1958+ {
1959+ if (LastStateChangeSeconds >= 5 .0f )
1960+ {
1961+ ShowFinalErrorMessage ();
1962+ }
1963+ else
1964+ {
1965+ if (LastStateChangeSeconds >= 2 .0f &&
1966+ GameClient ()->m_LocalServer .IsServerRunning ())
1967+ {
1968+ Client ()->Connect (" localhost" );
1969+ }
1970+
1971+ pProgressLabel = Localize (" Waiting for local server to start…" );
1972+ ProgressDeterminate = false ;
1973+ }
1974+ }
1975+
1976+ if (pProgressLabel != nullptr )
1977+ {
1978+ Ui ()->RenderProgressSpinner (ProgressIndicator.Center (), 12 .0f , {.m_Progress = ProgressDeterminate ? (LastStateChangeSeconds / RefreshDelay) : -1 .0f });
1979+ Ui ()->DoLabel (&ProgressLabel, pProgressLabel, 20 .0f , TEXTALIGN_ML);
1980+ }
1981+
1982+ static CButtonContainer s_Button;
1983+ if (DoButton_Menu (&s_Button, Localize (" Cancel" ), 0 , &ButtonBar) ||
1984+ Ui ()->ConsumeHotkey (CUi::HOTKEY_ESCAPE) ||
1985+ Ui ()->ConsumeHotkey (CUi::HOTKEY_ENTER))
1986+ {
1987+ m_Popup = POPUP_NONE;
1988+ }
1989+ }
18001990 else if (m_Popup == POPUP_POINTS)
18011991 {
18021992 Box.HSplitBottom (20 .0f , &Box, nullptr );
@@ -2576,3 +2766,13 @@ void CMenus::ShowQuitPopup()
25762766{
25772767 m_Popup = POPUP_QUIT;
25782768}
2769+
2770+ void CMenus::JoinTutorial ()
2771+ {
2772+ m_JoinTutorial.m_Queued = true ;
2773+ m_JoinTutorial.m_Status = CJoinTutorial::EStatus::REFRESHING;
2774+ m_JoinTutorial.m_TryRefresh = false ;
2775+ m_JoinTutorial.m_TriedRefresh = false ;
2776+ m_JoinTutorial.m_LocalServerState = CJoinTutorial::ELocalServerState::NOT_TRIED;
2777+ m_JoinTutorial.m_StateChange = time_get_nanoseconds ();
2778+ }
0 commit comments