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 <engine/graphics.h> |
4 | #include <engine/keys.h> |
5 | #include <engine/serverbrowser.h> |
6 | #include <engine/textrender.h> |
7 | |
8 | #include <engine/client/updater.h> |
9 | #include <engine/shared/config.h> |
10 | |
11 | #include <game/client/gameclient.h> |
12 | #include <game/client/ui.h> |
13 | |
14 | #include <game/generated/client_data.h> |
15 | #include <game/localization.h> |
16 | #include <game/version.h> |
17 | |
18 | #include "menus.h" |
19 | |
20 | void CMenus::(CUIRect MainView) |
21 | { |
22 | GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_START); |
23 | |
24 | // render logo |
25 | Graphics()->TextureSet(Texture: g_pData->m_aImages[IMAGE_BANNER].m_Id); |
26 | Graphics()->QuadsBegin(); |
27 | Graphics()->SetColor(r: 1, g: 1, b: 1, a: 1); |
28 | IGraphics::CQuadItem QuadItem(MainView.w / 2 - 170, 60, 360, 103); |
29 | Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1); |
30 | Graphics()->QuadsEnd(); |
31 | |
32 | const float Rounding = 10.0f; |
33 | const float VMargin = MainView.w / 2 - 190.0f; |
34 | |
35 | CUIRect Button; |
36 | int NewPage = -1; |
37 | |
38 | CUIRect ; |
39 | MainView.VSplitLeft(Cut: 30.0f, pLeft: 0, pRight: &ExtMenu); |
40 | ExtMenu.VSplitLeft(Cut: 100.0f, pLeft: &ExtMenu, pRight: 0); |
41 | |
42 | ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button); |
43 | static CButtonContainer s_DiscordButton; |
44 | if(DoButton_Menu(pButtonContainer: &s_DiscordButton, pText: Localize(pStr: "Discord" ), Checked: 0, pRect: &Button, pImageName: 0, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f))) |
45 | { |
46 | const char *pLink = Localize(pStr: "https://ddnet.org/discord" ); |
47 | if(!open_link(link: pLink)) |
48 | { |
49 | dbg_msg(sys: "menus" , fmt: "couldn't open link '%s'" , pLink); |
50 | } |
51 | } |
52 | |
53 | ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: 0); // little space |
54 | ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button); |
55 | static CButtonContainer s_LearnButton; |
56 | if(DoButton_Menu(pButtonContainer: &s_LearnButton, pText: Localize(pStr: "Learn" ), Checked: 0, pRect: &Button, pImageName: 0, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f))) |
57 | { |
58 | const char *pLink = Localize(pStr: "https://wiki.ddnet.org/" ); |
59 | if(!open_link(link: pLink)) |
60 | { |
61 | dbg_msg(sys: "menus" , fmt: "couldn't open link '%s'" , pLink); |
62 | } |
63 | } |
64 | |
65 | ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: 0); // little space |
66 | ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button); |
67 | static CButtonContainer s_TutorialButton; |
68 | static float s_JoinTutorialTime = 0.0f; |
69 | if(DoButton_Menu(pButtonContainer: &s_TutorialButton, pText: Localize(pStr: "Tutorial" ), Checked: 0, pRect: &Button, pImageName: 0, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || |
70 | (s_JoinTutorialTime != 0.0f && Client()->LocalTime() >= s_JoinTutorialTime)) |
71 | { |
72 | // Activate internet tab before joining tutorial to make sure the server info |
73 | // for the tutorial servers is available. |
74 | SetMenuPage(PAGE_INTERNET); |
75 | RefreshBrowserTab(Force: true); |
76 | const char *pAddr = ServerBrowser()->GetTutorialServer(); |
77 | if(pAddr) |
78 | { |
79 | Client()->Connect(pAddress: pAddr); |
80 | s_JoinTutorialTime = 0.0f; |
81 | } |
82 | else if(s_JoinTutorialTime == 0.0f) |
83 | { |
84 | dbg_msg(sys: "menus" , fmt: "couldn't find tutorial server, retrying in 5 seconds" ); |
85 | s_JoinTutorialTime = Client()->LocalTime() + 5.0f; |
86 | } |
87 | else |
88 | { |
89 | Client()->AddWarning(Warning: SWarning(Localize(pStr: "Can't find a Tutorial server" ))); |
90 | s_JoinTutorialTime = 0.0f; |
91 | } |
92 | } |
93 | |
94 | ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: 0); // little space |
95 | ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button); |
96 | static CButtonContainer s_WebsiteButton; |
97 | if(DoButton_Menu(pButtonContainer: &s_WebsiteButton, pText: Localize(pStr: "Website" ), Checked: 0, pRect: &Button, pImageName: 0, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f))) |
98 | { |
99 | const char *pLink = "https://ddnet.org/" ; |
100 | if(!open_link(link: pLink)) |
101 | { |
102 | dbg_msg(sys: "menus" , fmt: "couldn't open link '%s'" , pLink); |
103 | } |
104 | } |
105 | |
106 | ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: 0); // little space |
107 | ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button); |
108 | static CButtonContainer s_NewsButton; |
109 | if(DoButton_Menu(pButtonContainer: &s_NewsButton, pText: Localize(pStr: "News" ), Checked: 0, pRect: &Button, pImageName: 0, 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)) |
110 | NewPage = PAGE_NEWS; |
111 | |
112 | CUIRect ; |
113 | MainView.VMargin(Cut: VMargin, pOtherRect: &Menu); |
114 | Menu.HSplitBottom(Cut: 25.0f, pTop: &Menu, pBottom: 0); |
115 | |
116 | Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button); |
117 | static CButtonContainer s_QuitButton; |
118 | bool UsedEscape = false; |
119 | if(DoButton_Menu(pButtonContainer: &s_QuitButton, pText: Localize(pStr: "Quit" ), Checked: 0, pRect: &Button, pImageName: 0, 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)) |
120 | { |
121 | if(UsedEscape || m_pClient->Editor()->HasUnsavedData() || (Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmQuitTime && g_Config.m_ClConfirmQuitTime >= 0)) |
122 | { |
123 | m_Popup = POPUP_QUIT; |
124 | } |
125 | else |
126 | { |
127 | Client()->Quit(); |
128 | } |
129 | } |
130 | |
131 | Menu.HSplitBottom(Cut: 100.0f, pTop: &Menu, pBottom: 0); |
132 | Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button); |
133 | static CButtonContainer s_SettingsButton; |
134 | if(DoButton_Menu(pButtonContainer: &s_SettingsButton, pText: Localize(pStr: "Settings" ), Checked: 0, pRect: &Button, pImageName: g_Config.m_ClShowStartMenuImages ? "settings" : 0, Corners: IGraphics::CORNER_ALL, Rounding, FontFactor: 0.5f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(Key: KEY_S)) |
135 | NewPage = PAGE_SETTINGS; |
136 | |
137 | Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: 0); // little space |
138 | Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button); |
139 | static CButtonContainer s_LocalServerButton; |
140 | |
141 | if(!is_process_alive(process: m_ServerProcess.m_Process)) |
142 | KillServer(); |
143 | |
144 | if(DoButton_Menu(pButtonContainer: &s_LocalServerButton, pText: m_ServerProcess.m_Process ? Localize(pStr: "Stop server" ) : Localize(pStr: "Run server" ), Checked: 0, pRect: &Button, pImageName: g_Config.m_ClShowStartMenuImages ? "local_server" : 0, Corners: IGraphics::CORNER_ALL, Rounding, FontFactor: 0.5f, Color: m_ServerProcess.m_Process ? 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))) |
145 | { |
146 | if(m_ServerProcess.m_Process) |
147 | { |
148 | KillServer(); |
149 | } |
150 | else |
151 | { |
152 | char aBuf[IO_MAX_PATH_LENGTH]; |
153 | Storage()->GetBinaryPath(PLAT_SERVER_EXEC, pBuffer: aBuf, BufferSize: sizeof(aBuf)); |
154 | // No / in binary path means to search in $PATH, so it is expected that the file can't be opened. Just try executing anyway. |
155 | if(str_find(haystack: aBuf, needle: "/" ) == 0 || fs_is_file(path: aBuf)) |
156 | { |
157 | m_ServerProcess.m_Process = shell_execute(file: aBuf, window_state: EShellExecuteWindowState::BACKGROUND); |
158 | } |
159 | else |
160 | { |
161 | Client()->AddWarning(Warning: SWarning(Localize(pStr: "Server executable not found, can't run server" ))); |
162 | } |
163 | } |
164 | } |
165 | |
166 | static bool EditorHotkeyWasPressed = true; |
167 | static float EditorHotKeyChecktime = 0.0f; |
168 | Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: 0); // little space |
169 | Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button); |
170 | static CButtonContainer s_MapEditorButton; |
171 | if(DoButton_Menu(pButtonContainer: &s_MapEditorButton, pText: Localize(pStr: "Editor" ), Checked: 0, pRect: &Button, pImageName: g_Config.m_ClShowStartMenuImages ? "editor" : 0, Corners: IGraphics::CORNER_ALL, Rounding, FontFactor: 0.5f, Color: m_pClient->Editor()->HasUnsavedData() ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || (!EditorHotkeyWasPressed && Client()->LocalTime() - EditorHotKeyChecktime < 0.1f && CheckHotKey(Key: KEY_E))) |
172 | { |
173 | g_Config.m_ClEditor = 1; |
174 | Input()->MouseModeRelative(); |
175 | EditorHotkeyWasPressed = true; |
176 | } |
177 | if(!Input()->KeyIsPressed(Key: KEY_E)) |
178 | { |
179 | EditorHotkeyWasPressed = false; |
180 | EditorHotKeyChecktime = Client()->LocalTime(); |
181 | } |
182 | |
183 | Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: 0); // little space |
184 | Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button); |
185 | static CButtonContainer s_DemoButton; |
186 | if(DoButton_Menu(pButtonContainer: &s_DemoButton, pText: Localize(pStr: "Demos" ), Checked: 0, pRect: &Button, pImageName: g_Config.m_ClShowStartMenuImages ? "demos" : 0, Corners: IGraphics::CORNER_ALL, Rounding, FontFactor: 0.5f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(Key: KEY_D)) |
187 | { |
188 | NewPage = PAGE_DEMOS; |
189 | } |
190 | |
191 | Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: 0); // little space |
192 | Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button); |
193 | static CButtonContainer s_PlayButton; |
194 | if(DoButton_Menu(pButtonContainer: &s_PlayButton, pText: Localize(pStr: "Play" , pContext: "Start menu" ), Checked: 0, pRect: &Button, pImageName: g_Config.m_ClShowStartMenuImages ? "play_game" : 0, 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)) |
195 | { |
196 | NewPage = g_Config.m_UiPage >= PAGE_INTERNET && g_Config.m_UiPage <= PAGE_FAVORITE_COMMUNITY_5 ? g_Config.m_UiPage : PAGE_INTERNET; |
197 | } |
198 | |
199 | // render version |
200 | CUIRect VersionUpdate, CurVersion; |
201 | MainView.HSplitBottom(Cut: 20.0f, pTop: 0, pBottom: &VersionUpdate); |
202 | |
203 | VersionUpdate.VSplitRight(Cut: 50.0f, pLeft: &CurVersion, pRight: 0); |
204 | VersionUpdate.VMargin(Cut: VMargin, pOtherRect: &VersionUpdate); |
205 | |
206 | #if defined(CONF_AUTOUPDATE) |
207 | char aBuf[64]; |
208 | CUIRect Part; |
209 | int State = Updater()->GetCurrentState(); |
210 | bool NeedUpdate = str_comp(a: Client()->LatestVersion(), b: "0" ); |
211 | if(State == IUpdater::CLEAN && NeedUpdate) |
212 | { |
213 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "DDNet %s is out!" ), Client()->LatestVersion()); |
214 | TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f); |
215 | } |
216 | else if(State == IUpdater::CLEAN) |
217 | { |
218 | aBuf[0] = '\0'; |
219 | } |
220 | else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART) |
221 | { |
222 | char aCurrentFile[64]; |
223 | Updater()->GetCurrentFile(pBuf: aCurrentFile, BufSize: sizeof(aCurrentFile)); |
224 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "Downloading %s:" ), aCurrentFile); |
225 | } |
226 | else if(State == IUpdater::FAIL) |
227 | { |
228 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "Update failed! Check log…" )); |
229 | TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f); |
230 | } |
231 | else if(State == IUpdater::NEED_RESTART) |
232 | { |
233 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "DDNet Client updated!" )); |
234 | TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f); |
235 | } |
236 | Ui()->DoLabel(pRect: &VersionUpdate, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML); |
237 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
238 | |
239 | VersionUpdate.VSplitLeft(Cut: TextRender()->TextWidth(Size: 14.0f, pText: aBuf, StrLength: -1, LineWidth: -1.0f) + 10.0f, pLeft: 0, pRight: &Part); |
240 | |
241 | if(State == IUpdater::CLEAN && NeedUpdate) |
242 | { |
243 | CUIRect Update; |
244 | Part.VSplitLeft(Cut: 100.0f, pLeft: &Update, NULL); |
245 | |
246 | static CButtonContainer s_VersionUpdate; |
247 | if(DoButton_Menu(pButtonContainer: &s_VersionUpdate, pText: Localize(pStr: "Update now" ), Checked: 0, pRect: &Update, pImageName: 0, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f))) |
248 | { |
249 | Updater()->InitiateUpdate(); |
250 | } |
251 | } |
252 | else if(State == IUpdater::NEED_RESTART) |
253 | { |
254 | CUIRect Restart; |
255 | Part.VSplitLeft(Cut: 50.0f, pLeft: &Restart, pRight: &Part); |
256 | |
257 | static CButtonContainer s_VersionUpdate; |
258 | if(DoButton_Menu(pButtonContainer: &s_VersionUpdate, pText: Localize(pStr: "Restart" ), Checked: 0, pRect: &Restart, pImageName: 0, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f))) |
259 | { |
260 | Client()->Restart(); |
261 | } |
262 | } |
263 | else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART) |
264 | { |
265 | CUIRect ProgressBar, Percent; |
266 | Part.VSplitLeft(Cut: 100.0f, pLeft: &ProgressBar, pRight: &Percent); |
267 | ProgressBar.y += 2.0f; |
268 | ProgressBar.HMargin(Cut: 1.0f, pOtherRect: &ProgressBar); |
269 | ProgressBar.Draw(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 5.0f); |
270 | ProgressBar.w = clamp(val: (float)Updater()->GetCurrentPercent(), lo: 10.0f, hi: 100.0f); |
271 | ProgressBar.Draw(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f), Corners: IGraphics::CORNER_ALL, Rounding: 5.0f); |
272 | } |
273 | #elif defined(CONF_INFORM_UPDATE) |
274 | if(str_comp(Client()->LatestVersion(), "0" ) != 0) |
275 | { |
276 | char aBuf[64]; |
277 | str_format(aBuf, sizeof(aBuf), Localize("DDNet %s is out!" ), Client()->LatestVersion()); |
278 | TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); |
279 | Ui()->DoLabel(&VersionUpdate, aBuf, 14.0f, TEXTALIGN_MC); |
280 | TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); |
281 | } |
282 | #endif |
283 | |
284 | Ui()->DoLabel(pRect: &CurVersion, GAME_RELEASE_VERSION, Size: 14.0f, Align: TEXTALIGN_MR); |
285 | |
286 | if(NewPage != -1) |
287 | { |
288 | m_MenuPage = NewPage; |
289 | m_ShowStart = false; |
290 | } |
291 | } |
292 | |
293 | void CMenus::() |
294 | { |
295 | if(m_ServerProcess.m_Process) |
296 | { |
297 | if(kill_process(process: m_ServerProcess.m_Process)) |
298 | { |
299 | m_ServerProcess.m_Process = INVALID_PROCESS; |
300 | } |
301 | } |
302 | } |
303 | |