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/font_icons.h>
7#include <engine/graphics.h>
8#include <engine/keys.h>
9#include <engine/serverbrowser.h>
10#include <engine/shared/config.h>
11#include <engine/textrender.h>
12
13#include <generated/client_data.h>
14
15#include <game/client/gameclient.h>
16#include <game/client/ui.h>
17#include <game/localization.h>
18#include <game/version.h>
19
20#if defined(CONF_PLATFORM_ANDROID)
21#include <android/android_main.h>
22#endif
23
24void CMenusStart::RenderStartMenu(CUIRect MainView)
25{
26 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_START);
27
28 // render logo
29 Graphics()->TextureSet(Texture: g_pData->m_aImages[IMAGE_BANNER].m_Id);
30 Graphics()->QuadsBegin();
31 Graphics()->SetColor(r: 1, g: 1, b: 1, a: 1);
32 IGraphics::CQuadItem QuadItem(MainView.w / 2 - 170, 60, 360, 103);
33 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
34 Graphics()->QuadsEnd();
35
36 const float Rounding = 10.0f;
37 const float VMargin = MainView.w / 2 - 190.0f;
38
39 CUIRect Button;
40 int NewPage = -1;
41
42 CUIRect ExtMenu;
43 MainView.VSplitLeft(Cut: 30.0f, pLeft: nullptr, pRight: &ExtMenu);
44 ExtMenu.VSplitLeft(Cut: 100.0f, pLeft: &ExtMenu, pRight: nullptr);
45
46 ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button);
47 static CButtonContainer s_DiscordButton;
48 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)))
49 {
50 Client()->ViewLink(pLink: Localize(pStr: "https://ddnet.org/discord"));
51 }
52
53 ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: nullptr); // little space
54 ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button);
55 static CButtonContainer s_LearnButton;
56 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)))
57 {
58 Client()->ViewLink(pLink: Localize(pStr: "https://wiki.ddnet.org/"));
59 }
60
61 ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: nullptr); // little space
62 ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button);
63 static CButtonContainer s_TutorialButton;
64 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)))
65 {
66 GameClient()->m_Menus.JoinTutorial();
67 }
68
69 ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: nullptr); // little space
70 ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button);
71 static CButtonContainer s_WebsiteButton;
72 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)))
73 {
74 Client()->ViewLink(pLink: "https://ddnet.org/");
75 }
76
77 ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: nullptr); // little space
78 ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button);
79 static CButtonContainer s_NewsButton;
80 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))
81 NewPage = CMenus::PAGE_NEWS;
82
83 CUIRect Menu;
84 MainView.VMargin(Cut: VMargin, pOtherRect: &Menu);
85 Menu.HSplitBottom(Cut: 25.0f, pTop: &Menu, pBottom: nullptr);
86
87 Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button);
88 static CButtonContainer s_QuitButton;
89 bool UsedEscape = false;
90 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))
91 {
92 if(UsedEscape || GameClient()->Editor()->HasUnsavedData() || (GameClient()->CurrentRaceTime() / 60 >= g_Config.m_ClConfirmQuitTime && g_Config.m_ClConfirmQuitTime >= 0))
93 {
94 GameClient()->m_Menus.ShowQuitPopup();
95 }
96 else
97 {
98 Client()->Quit();
99 }
100 }
101
102 Menu.HSplitBottom(Cut: 100.0f, pTop: &Menu, pBottom: nullptr);
103 Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button);
104 static CButtonContainer s_SettingsButton;
105 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))
106 NewPage = CMenus::PAGE_SETTINGS;
107
108 Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: nullptr); // little space
109 Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button);
110 static CButtonContainer s_LocalServerButton;
111
112 const bool LocalServerRunning = GameClient()->m_LocalServer.IsServerRunning();
113 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)))
114 {
115 if(LocalServerRunning)
116 {
117 GameClient()->m_LocalServer.KillServer();
118 }
119 else
120 {
121 GameClient()->m_LocalServer.RunServer(vpArguments: {});
122 }
123 }
124
125 Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: nullptr); // little space
126 Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button);
127 static CButtonContainer s_MapEditorButton;
128 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))
129 {
130 g_Config.m_ClEditor = 1;
131 Input()->MouseModeRelative();
132 }
133
134 Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: nullptr); // little space
135 Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button);
136 static CButtonContainer s_DemoButton;
137 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))
138 {
139 NewPage = CMenus::PAGE_DEMOS;
140 }
141
142 Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: nullptr); // little space
143 Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button);
144 static CButtonContainer s_PlayButton;
145 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))
146 {
147 NewPage = g_Config.m_UiPage >= CMenus::PAGE_INTERNET && g_Config.m_UiPage <= CMenus::PAGE_FAVORITE_COMMUNITY_5 ? g_Config.m_UiPage : CMenus::PAGE_INTERNET;
148 }
149
150 // render version
151 CUIRect CurVersion, ConsoleButton;
152 MainView.HSplitBottom(Cut: 45.0f, pTop: nullptr, pBottom: &CurVersion);
153 CurVersion.VSplitRight(Cut: 40.0f, pLeft: &CurVersion, pRight: nullptr);
154 CurVersion.HSplitTop(Cut: 20.0f, pTop: &ConsoleButton, pBottom: &CurVersion);
155 CurVersion.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &CurVersion);
156 ConsoleButton.VSplitRight(Cut: 40.0f, pLeft: nullptr, pRight: &ConsoleButton);
157 Ui()->DoLabel(pRect: &CurVersion, GAME_RELEASE_VERSION, Size: 14.0f, Align: TEXTALIGN_MR);
158
159 static CButtonContainer s_ConsoleButton;
160 TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
161 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);
162 if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_ConsoleButton, pText: FontIcon::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)))
163 {
164 GameClient()->m_GameConsole.Toggle(Type: CGameConsole::CONSOLETYPE_LOCAL);
165 }
166 TextRender()->SetRenderFlags(0);
167 TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
168
169 CUIRect VersionUpdate;
170 MainView.HSplitBottom(Cut: 20.0f, pTop: nullptr, pBottom: &VersionUpdate);
171 VersionUpdate.VMargin(Cut: VMargin, pOtherRect: &VersionUpdate);
172#if defined(CONF_AUTOUPDATE)
173 CUIRect UpdateButton;
174 VersionUpdate.VSplitRight(Cut: 100.0f, pLeft: &VersionUpdate, pRight: &UpdateButton);
175 VersionUpdate.VSplitRight(Cut: 10.0f, pLeft: &VersionUpdate, pRight: nullptr);
176
177 char aBuf[128];
178 const IUpdater::EUpdaterState State = Updater()->GetCurrentState();
179 const bool NeedUpdate = str_comp(a: Client()->LatestVersion(), b: "0");
180
181 if(State == IUpdater::CLEAN && NeedUpdate)
182 {
183 static CButtonContainer s_VersionUpdate;
184 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)))
185 {
186 Updater()->InitiateUpdate();
187 }
188 }
189 else if(State == IUpdater::NEED_RESTART)
190 {
191 static CButtonContainer s_VersionUpdate;
192 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)))
193 {
194 Client()->Restart();
195 }
196 }
197 else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART)
198 {
199 Ui()->RenderProgressBar(ProgressBar: UpdateButton, Progress: Updater()->GetCurrentPercent() / 100.0f);
200 }
201
202 if(State == IUpdater::CLEAN && NeedUpdate)
203 {
204 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "DDNet %s is out!"), Client()->LatestVersion());
205 TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f);
206 }
207 else if(State == IUpdater::CLEAN)
208 {
209 aBuf[0] = '\0';
210 }
211 else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART)
212 {
213 char aCurrentFile[64];
214 Updater()->GetCurrentFile(pBuf: aCurrentFile, BufSize: sizeof(aCurrentFile));
215 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "Downloading %s:"), aCurrentFile);
216 }
217 else if(State == IUpdater::FAIL)
218 {
219 str_copy(dst&: aBuf, src: Localize(pStr: "Update failed! Check log…"));
220 TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f);
221 }
222 else if(State == IUpdater::NEED_RESTART)
223 {
224 str_copy(dst&: aBuf, src: Localize(pStr: "DDNet Client updated!"));
225 TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f);
226 }
227 Ui()->DoLabel(pRect: &VersionUpdate, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML);
228 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
229#elif defined(CONF_INFORM_UPDATE)
230 if(str_comp(Client()->LatestVersion(), "0") != 0)
231 {
232 CUIRect DownloadButton;
233 VersionUpdate.VSplitRight(100.0f, &VersionUpdate, &DownloadButton);
234 VersionUpdate.VSplitRight(10.0f, &VersionUpdate, nullptr);
235
236 static CButtonContainer s_DownloadButton;
237 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)))
238 {
239 Client()->ViewLink("https://ddnet.org/downloads/");
240 }
241
242 char aBuf[64];
243 str_format(aBuf, sizeof(aBuf), Localize("DDNet %s is out!"), Client()->LatestVersion());
244 SLabelProperties UpdateLabelProps;
245 UpdateLabelProps.SetColor(ColorRGBA(1.0f, 0.4f, 0.4f, 1.0f));
246 Ui()->DoLabel(&VersionUpdate, aBuf, 14.0f, TEXTALIGN_ML, UpdateLabelProps);
247 }
248#endif
249
250 if(NewPage != -1)
251 {
252 GameClient()->m_Menus.SetShowStart(false);
253 GameClient()->m_Menus.SetMenuPage(NewPage);
254 }
255}
256
257bool CMenusStart::CheckHotKey(int Key) const
258{
259 return !Input()->ShiftIsPressed() && !Input()->ModifierIsPressed() && !Input()->AltIsPressed() && // no modifier
260 Input()->KeyPress(Key) &&
261 !GameClient()->m_GameConsole.IsActive();
262}
263