1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "menus_start.h"
4
5#include <engine/client/updater.h>
6#include <engine/graphics.h>
7#include <engine/keys.h>
8#include <engine/serverbrowser.h>
9#include <engine/shared/config.h>
10#include <engine/textrender.h>
11
12#include <generated/client_data.h>
13
14#include <game/client/gameclient.h>
15#include <game/client/ui.h>
16#include <game/localization.h>
17#include <game/version.h>
18
19#if defined(CONF_PLATFORM_ANDROID)
20#include <android/android_main.h>
21#endif
22
23using namespace FontIcons;
24
25void CMenusStart::RenderStartMenu(CUIRect MainView)
26{
27 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_START);
28
29 // render logo
30 Graphics()->TextureSet(Texture: g_pData->m_aImages[IMAGE_BANNER].m_Id);
31 Graphics()->QuadsBegin();
32 Graphics()->SetColor(r: 1, g: 1, b: 1, a: 1);
33 IGraphics::CQuadItem QuadItem(MainView.w / 2 - 170, 60, 360, 103);
34 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
35 Graphics()->QuadsEnd();
36
37 const float Rounding = 10.0f;
38 const float VMargin = MainView.w / 2 - 190.0f;
39
40 CUIRect Button;
41 int NewPage = -1;
42
43 CUIRect ExtMenu;
44 MainView.VSplitLeft(Cut: 30.0f, pLeft: nullptr, pRight: &ExtMenu);
45 ExtMenu.VSplitLeft(Cut: 100.0f, pLeft: &ExtMenu, pRight: nullptr);
46
47 ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button);
48 static CButtonContainer s_DiscordButton;
49 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_DiscordButton, pText: Localize(pStr: "Discord"), Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)))
50 {
51 Client()->ViewLink(pLink: Localize(pStr: "https://ddnet.org/discord"));
52 }
53
54 ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: nullptr); // little space
55 ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button);
56 static CButtonContainer s_LearnButton;
57 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_LearnButton, pText: Localize(pStr: "Learn"), Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)))
58 {
59 Client()->ViewLink(pLink: Localize(pStr: "https://wiki.ddnet.org/"));
60 }
61
62 ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: nullptr); // little space
63 ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button);
64 static CButtonContainer s_TutorialButton;
65 static float s_JoinTutorialTime = 0.0f;
66 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_TutorialButton, pText: Localize(pStr: "Tutorial"), Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) ||
67 (s_JoinTutorialTime != 0.0f && Client()->LocalTime() >= s_JoinTutorialTime))
68 {
69 // Activate internet tab before joining tutorial to make sure the server info
70 // for the tutorial servers is available.
71 GameClient()->m_Menus.SetMenuPage(CMenus::PAGE_INTERNET);
72 GameClient()->m_Menus.RefreshBrowserTab(Force: true);
73 const char *pAddr = ServerBrowser()->GetTutorialServer();
74 if(pAddr)
75 {
76 Client()->Connect(pAddress: pAddr);
77 s_JoinTutorialTime = 0.0f;
78 }
79 else if(s_JoinTutorialTime == 0.0f)
80 {
81 dbg_msg(sys: "menus", fmt: "couldn't find tutorial server, retrying in 5 seconds");
82 s_JoinTutorialTime = Client()->LocalTime() + 5.0f;
83 }
84 else
85 {
86 Client()->AddWarning(Warning: SWarning(Localize(pStr: "Can't find a Tutorial server")));
87 s_JoinTutorialTime = 0.0f;
88 }
89 }
90
91 ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: nullptr); // little space
92 ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button);
93 static CButtonContainer s_WebsiteButton;
94 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_WebsiteButton, pText: Localize(pStr: "Website"), Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)))
95 {
96 Client()->ViewLink(pLink: "https://ddnet.org/");
97 }
98
99 ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: nullptr); // little space
100 ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button);
101 static CButtonContainer s_NewsButton;
102 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_NewsButton, pText: Localize(pStr: "News"), Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: g_Config.m_UiUnreadNews ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(Key: KEY_N))
103 NewPage = CMenus::PAGE_NEWS;
104
105 CUIRect Menu;
106 MainView.VMargin(Cut: VMargin, pOtherRect: &Menu);
107 Menu.HSplitBottom(Cut: 25.0f, pTop: &Menu, pBottom: nullptr);
108
109 Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button);
110 static CButtonContainer s_QuitButton;
111 bool UsedEscape = false;
112 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_QuitButton, pText: Localize(pStr: "Quit"), Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_ALL, Rounding, FontFactor: 0.5f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || (UsedEscape = Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_ESCAPE)) || CheckHotKey(Key: KEY_Q))
113 {
114 if(UsedEscape || GameClient()->Editor()->HasUnsavedData() || (GameClient()->CurrentRaceTime() / 60 >= g_Config.m_ClConfirmQuitTime && g_Config.m_ClConfirmQuitTime >= 0))
115 {
116 GameClient()->m_Menus.ShowQuitPopup();
117 }
118 else
119 {
120 Client()->Quit();
121 }
122 }
123
124 Menu.HSplitBottom(Cut: 100.0f, pTop: &Menu, pBottom: nullptr);
125 Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button);
126 static CButtonContainer s_SettingsButton;
127 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_SettingsButton, pText: Localize(pStr: "Settings"), Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pImageName: g_Config.m_ClShowStartMenuImages ? "settings" : nullptr, Corners: IGraphics::CORNER_ALL, Rounding, FontFactor: 0.5f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(Key: KEY_S))
128 NewPage = CMenus::PAGE_SETTINGS;
129
130 Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: nullptr); // little space
131 Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button);
132 static CButtonContainer s_LocalServerButton;
133
134 const bool LocalServerRunning = GameClient()->m_LocalServer.IsServerRunning();
135 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_LocalServerButton, pText: LocalServerRunning ? Localize(pStr: "Stop server") : Localize(pStr: "Run server"), Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pImageName: g_Config.m_ClShowStartMenuImages ? "local_server" : nullptr, Corners: IGraphics::CORNER_ALL, Rounding, FontFactor: 0.5f, Color: LocalServerRunning ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || (CheckHotKey(Key: KEY_R) && Input()->KeyPress(Key: KEY_R)))
136 {
137 if(LocalServerRunning)
138 {
139 GameClient()->m_LocalServer.KillServer();
140 }
141 else
142 {
143 GameClient()->m_LocalServer.RunServer(vpArguments: {});
144 }
145 }
146
147 Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: nullptr); // little space
148 Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button);
149 static CButtonContainer s_MapEditorButton;
150 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_MapEditorButton, pText: Localize(pStr: "Editor"), Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pImageName: g_Config.m_ClShowStartMenuImages ? "editor" : nullptr, Corners: IGraphics::CORNER_ALL, Rounding, FontFactor: 0.5f, Color: GameClient()->Editor()->HasUnsavedData() ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(Key: KEY_E))
151 {
152 g_Config.m_ClEditor = 1;
153 Input()->MouseModeRelative();
154 }
155
156 Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: nullptr); // little space
157 Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button);
158 static CButtonContainer s_DemoButton;
159 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_DemoButton, pText: Localize(pStr: "Demos"), Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pImageName: g_Config.m_ClShowStartMenuImages ? "demos" : nullptr, Corners: IGraphics::CORNER_ALL, Rounding, FontFactor: 0.5f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(Key: KEY_D))
160 {
161 NewPage = CMenus::PAGE_DEMOS;
162 }
163
164 Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: nullptr); // little space
165 Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button);
166 static CButtonContainer s_PlayButton;
167 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_PlayButton, pText: Localize(pStr: "Play", pContext: "Start menu"), Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pImageName: g_Config.m_ClShowStartMenuImages ? "play_game" : nullptr, Corners: IGraphics::CORNER_ALL, Rounding, FontFactor: 0.5f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_ENTER) || CheckHotKey(Key: KEY_P))
168 {
169 NewPage = g_Config.m_UiPage >= CMenus::PAGE_INTERNET && g_Config.m_UiPage <= CMenus::PAGE_FAVORITE_COMMUNITY_5 ? g_Config.m_UiPage : CMenus::PAGE_INTERNET;
170 }
171
172 // render version
173 CUIRect CurVersion, ConsoleButton;
174 MainView.HSplitBottom(Cut: 45.0f, pTop: nullptr, pBottom: &CurVersion);
175 CurVersion.VSplitRight(Cut: 40.0f, pLeft: &CurVersion, pRight: nullptr);
176 CurVersion.HSplitTop(Cut: 20.0f, pTop: &ConsoleButton, pBottom: &CurVersion);
177 CurVersion.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &CurVersion);
178 ConsoleButton.VSplitRight(Cut: 40.0f, pLeft: nullptr, pRight: &ConsoleButton);
179 Ui()->DoLabel(pRect: &CurVersion, GAME_RELEASE_VERSION, Size: 14.0f, Align: TEXTALIGN_MR);
180
181 static CButtonContainer s_ConsoleButton;
182 TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
183 TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGNMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
184 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_ConsoleButton, pText: FONT_ICON_TERMINAL, Checked: 0, pRect: &ConsoleButton, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.1f)))
185 {
186 GameClient()->m_GameConsole.Toggle(Type: CGameConsole::CONSOLETYPE_LOCAL);
187 }
188 TextRender()->SetRenderFlags(0);
189 TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
190
191 CUIRect VersionUpdate;
192 MainView.HSplitBottom(Cut: 20.0f, pTop: nullptr, pBottom: &VersionUpdate);
193 VersionUpdate.VMargin(Cut: VMargin, pOtherRect: &VersionUpdate);
194#if defined(CONF_AUTOUPDATE)
195 CUIRect UpdateButton;
196 VersionUpdate.VSplitRight(Cut: 100.0f, pLeft: &VersionUpdate, pRight: &UpdateButton);
197 VersionUpdate.VSplitRight(Cut: 10.0f, pLeft: &VersionUpdate, pRight: nullptr);
198
199 char aBuf[128];
200 const IUpdater::EUpdaterState State = Updater()->GetCurrentState();
201 const bool NeedUpdate = str_comp(a: Client()->LatestVersion(), b: "0");
202
203 if(State == IUpdater::CLEAN && NeedUpdate)
204 {
205 static CButtonContainer s_VersionUpdate;
206 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_VersionUpdate, pText: Localize(pStr: "Update now"), Checked: 0, pRect: &UpdateButton, Flags: BUTTONFLAG_LEFT, pImageName: 0, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)))
207 {
208 Updater()->InitiateUpdate();
209 }
210 }
211 else if(State == IUpdater::NEED_RESTART)
212 {
213 static CButtonContainer s_VersionUpdate;
214 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_VersionUpdate, pText: Localize(pStr: "Restart"), Checked: 0, pRect: &UpdateButton, Flags: BUTTONFLAG_LEFT, pImageName: 0, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)))
215 {
216 Client()->Restart();
217 }
218 }
219 else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART)
220 {
221 Ui()->RenderProgressBar(ProgressBar: UpdateButton, Progress: Updater()->GetCurrentPercent() / 100.0f);
222 }
223
224 if(State == IUpdater::CLEAN && NeedUpdate)
225 {
226 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "DDNet %s is out!"), Client()->LatestVersion());
227 TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f);
228 }
229 else if(State == IUpdater::CLEAN)
230 {
231 aBuf[0] = '\0';
232 }
233 else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART)
234 {
235 char aCurrentFile[64];
236 Updater()->GetCurrentFile(pBuf: aCurrentFile, BufSize: sizeof(aCurrentFile));
237 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "Downloading %s:"), aCurrentFile);
238 }
239 else if(State == IUpdater::FAIL)
240 {
241 str_copy(dst&: aBuf, src: Localize(pStr: "Update failed! Check log…"));
242 TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f);
243 }
244 else if(State == IUpdater::NEED_RESTART)
245 {
246 str_copy(dst&: aBuf, src: Localize(pStr: "DDNet Client updated!"));
247 TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f);
248 }
249 Ui()->DoLabel(pRect: &VersionUpdate, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML);
250 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
251#elif defined(CONF_INFORM_UPDATE)
252 if(str_comp(Client()->LatestVersion(), "0") != 0)
253 {
254 CUIRect DownloadButton;
255 VersionUpdate.VSplitRight(100.0f, &VersionUpdate, &DownloadButton);
256 VersionUpdate.VSplitRight(10.0f, &VersionUpdate, nullptr);
257
258 static CButtonContainer s_DownloadButton;
259 if(GameClient()->m_Menus.DoButton_Menu(&s_DownloadButton, Localize("Download"), 0, &DownloadButton, BUTTONFLAG_LEFT, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)))
260 {
261 Client()->ViewLink("https://ddnet.org/downloads/");
262 }
263
264 char aBuf[64];
265 str_format(aBuf, sizeof(aBuf), Localize("DDNet %s is out!"), Client()->LatestVersion());
266 SLabelProperties UpdateLabelProps;
267 UpdateLabelProps.SetColor(ColorRGBA(1.0f, 0.4f, 0.4f, 1.0f));
268 Ui()->DoLabel(&VersionUpdate, aBuf, 14.0f, TEXTALIGN_ML, UpdateLabelProps);
269 }
270#endif
271
272 if(NewPage != -1)
273 {
274 GameClient()->m_Menus.SetShowStart(false);
275 GameClient()->m_Menus.SetMenuPage(NewPage);
276 }
277}
278
279bool CMenusStart::CheckHotKey(int Key) const
280{
281 return !Input()->ShiftIsPressed() && !Input()->ModifierIsPressed() && !Input()->AltIsPressed() && // no modifier
282 Input()->KeyPress(Key) &&
283 !GameClient()->m_GameConsole.IsActive();
284}
285