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 | Client()->ViewLink(pLink: Localize(pStr: "https://ddnet.org/discord" )); |
47 | } |
48 | |
49 | ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: 0); // little space |
50 | ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button); |
51 | static CButtonContainer s_LearnButton; |
52 | 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))) |
53 | { |
54 | Client()->ViewLink(pLink: Localize(pStr: "https://wiki.ddnet.org/" )); |
55 | } |
56 | |
57 | ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: 0); // little space |
58 | ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button); |
59 | static CButtonContainer s_TutorialButton; |
60 | static float s_JoinTutorialTime = 0.0f; |
61 | 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)) || |
62 | (s_JoinTutorialTime != 0.0f && Client()->LocalTime() >= s_JoinTutorialTime)) |
63 | { |
64 | // Activate internet tab before joining tutorial to make sure the server info |
65 | // for the tutorial servers is available. |
66 | SetMenuPage(PAGE_INTERNET); |
67 | RefreshBrowserTab(Force: true); |
68 | const char *pAddr = ServerBrowser()->GetTutorialServer(); |
69 | if(pAddr) |
70 | { |
71 | Client()->Connect(pAddress: pAddr); |
72 | s_JoinTutorialTime = 0.0f; |
73 | } |
74 | else if(s_JoinTutorialTime == 0.0f) |
75 | { |
76 | dbg_msg(sys: "menus" , fmt: "couldn't find tutorial server, retrying in 5 seconds" ); |
77 | s_JoinTutorialTime = Client()->LocalTime() + 5.0f; |
78 | } |
79 | else |
80 | { |
81 | Client()->AddWarning(Warning: SWarning(Localize(pStr: "Can't find a Tutorial server" ))); |
82 | s_JoinTutorialTime = 0.0f; |
83 | } |
84 | } |
85 | |
86 | ExtMenu.HSplitBottom(Cut: 5.0f, pTop: &ExtMenu, pBottom: 0); // little space |
87 | ExtMenu.HSplitBottom(Cut: 20.0f, pTop: &ExtMenu, pBottom: &Button); |
88 | static CButtonContainer s_WebsiteButton; |
89 | 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))) |
90 | { |
91 | Client()->ViewLink(pLink: "https://ddnet.org/" ); |
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_NewsButton; |
97 | 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)) |
98 | NewPage = PAGE_NEWS; |
99 | |
100 | CUIRect ; |
101 | MainView.VMargin(Cut: VMargin, pOtherRect: &Menu); |
102 | Menu.HSplitBottom(Cut: 25.0f, pTop: &Menu, pBottom: 0); |
103 | |
104 | Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button); |
105 | static CButtonContainer s_QuitButton; |
106 | bool UsedEscape = false; |
107 | 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)) |
108 | { |
109 | if(UsedEscape || m_pClient->Editor()->HasUnsavedData() || (Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmQuitTime && g_Config.m_ClConfirmQuitTime >= 0)) |
110 | { |
111 | m_Popup = POPUP_QUIT; |
112 | } |
113 | else |
114 | { |
115 | Client()->Quit(); |
116 | } |
117 | } |
118 | |
119 | Menu.HSplitBottom(Cut: 100.0f, pTop: &Menu, pBottom: 0); |
120 | Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button); |
121 | static CButtonContainer s_SettingsButton; |
122 | 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)) |
123 | NewPage = PAGE_SETTINGS; |
124 | |
125 | Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: 0); // little space |
126 | Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button); |
127 | static CButtonContainer s_LocalServerButton; |
128 | |
129 | if(!is_process_alive(process: m_ServerProcess.m_Process)) |
130 | KillServer(); |
131 | |
132 | 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))) |
133 | { |
134 | if(m_ServerProcess.m_Process) |
135 | { |
136 | KillServer(); |
137 | } |
138 | else |
139 | { |
140 | char aBuf[IO_MAX_PATH_LENGTH]; |
141 | Storage()->GetBinaryPath(PLAT_SERVER_EXEC, pBuffer: aBuf, BufferSize: sizeof(aBuf)); |
142 | // No / in binary path means to search in $PATH, so it is expected that the file can't be opened. Just try executing anyway. |
143 | if(str_find(haystack: aBuf, needle: "/" ) == 0 || fs_is_file(path: aBuf)) |
144 | { |
145 | m_ServerProcess.m_Process = shell_execute(file: aBuf, window_state: EShellExecuteWindowState::BACKGROUND); |
146 | m_ForceRefreshLanPage = true; |
147 | } |
148 | else |
149 | { |
150 | Client()->AddWarning(Warning: SWarning(Localize(pStr: "Server executable not found, can't run server" ))); |
151 | } |
152 | } |
153 | } |
154 | |
155 | static bool EditorHotkeyWasPressed = true; |
156 | static float EditorHotKeyChecktime = 0.0f; |
157 | Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: 0); // little space |
158 | Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button); |
159 | static CButtonContainer s_MapEditorButton; |
160 | 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))) |
161 | { |
162 | g_Config.m_ClEditor = 1; |
163 | Input()->MouseModeRelative(); |
164 | EditorHotkeyWasPressed = true; |
165 | } |
166 | if(!Input()->KeyIsPressed(Key: KEY_E)) |
167 | { |
168 | EditorHotkeyWasPressed = false; |
169 | EditorHotKeyChecktime = Client()->LocalTime(); |
170 | } |
171 | |
172 | Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: 0); // little space |
173 | Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button); |
174 | static CButtonContainer s_DemoButton; |
175 | 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)) |
176 | { |
177 | NewPage = PAGE_DEMOS; |
178 | } |
179 | |
180 | Menu.HSplitBottom(Cut: 5.0f, pTop: &Menu, pBottom: 0); // little space |
181 | Menu.HSplitBottom(Cut: 40.0f, pTop: &Menu, pBottom: &Button); |
182 | static CButtonContainer s_PlayButton; |
183 | 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)) |
184 | { |
185 | NewPage = g_Config.m_UiPage >= PAGE_INTERNET && g_Config.m_UiPage <= PAGE_FAVORITE_COMMUNITY_5 ? g_Config.m_UiPage : PAGE_INTERNET; |
186 | } |
187 | |
188 | // render version |
189 | CUIRect VersionUpdate, CurVersion; |
190 | MainView.HSplitBottom(Cut: 20.0f, pTop: nullptr, pBottom: &VersionUpdate); |
191 | VersionUpdate.VSplitRight(Cut: 50.0f, pLeft: &CurVersion, pRight: nullptr); |
192 | VersionUpdate.VMargin(Cut: VMargin, pOtherRect: &VersionUpdate); |
193 | |
194 | Ui()->DoLabel(pRect: &CurVersion, GAME_RELEASE_VERSION, Size: 14.0f, Align: TEXTALIGN_MR); |
195 | |
196 | #if defined(CONF_AUTOUPDATE) |
197 | CUIRect UpdateButton; |
198 | VersionUpdate.VSplitRight(Cut: 100.0f, pLeft: &VersionUpdate, pRight: &UpdateButton); |
199 | VersionUpdate.VSplitRight(Cut: 10.0f, pLeft: &VersionUpdate, pRight: nullptr); |
200 | |
201 | char aBuf[128]; |
202 | const IUpdater::EUpdaterState State = Updater()->GetCurrentState(); |
203 | const bool NeedUpdate = str_comp(a: Client()->LatestVersion(), b: "0" ); |
204 | |
205 | if(State == IUpdater::CLEAN && NeedUpdate) |
206 | { |
207 | static CButtonContainer s_VersionUpdate; |
208 | if(DoButton_Menu(pButtonContainer: &s_VersionUpdate, pText: Localize(pStr: "Update now" ), Checked: 0, pRect: &UpdateButton, pImageName: 0, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f))) |
209 | { |
210 | Updater()->InitiateUpdate(); |
211 | } |
212 | } |
213 | else if(State == IUpdater::NEED_RESTART) |
214 | { |
215 | static CButtonContainer s_VersionUpdate; |
216 | if(DoButton_Menu(pButtonContainer: &s_VersionUpdate, pText: Localize(pStr: "Restart" ), Checked: 0, pRect: &UpdateButton, pImageName: 0, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f))) |
217 | { |
218 | Client()->Restart(); |
219 | } |
220 | } |
221 | else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART) |
222 | { |
223 | Ui()->RenderProgressBar(ProgressBar: UpdateButton, Progress: Updater()->GetCurrentPercent() / 100.0f); |
224 | } |
225 | |
226 | if(State == IUpdater::CLEAN && NeedUpdate) |
227 | { |
228 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "DDNet %s is out!" ), Client()->LatestVersion()); |
229 | TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f); |
230 | } |
231 | else if(State == IUpdater::CLEAN) |
232 | { |
233 | aBuf[0] = '\0'; |
234 | } |
235 | else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART) |
236 | { |
237 | char aCurrentFile[64]; |
238 | Updater()->GetCurrentFile(pBuf: aCurrentFile, BufSize: sizeof(aCurrentFile)); |
239 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "Downloading %s:" ), aCurrentFile); |
240 | } |
241 | else if(State == IUpdater::FAIL) |
242 | { |
243 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "Update failed! Check log…" )); |
244 | TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f); |
245 | } |
246 | else if(State == IUpdater::NEED_RESTART) |
247 | { |
248 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "DDNet Client updated!" )); |
249 | TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f); |
250 | } |
251 | Ui()->DoLabel(pRect: &VersionUpdate, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML); |
252 | TextRender()->TextColor(rgb: TextRender()->DefaultTextColor()); |
253 | #elif defined(CONF_INFORM_UPDATE) |
254 | if(str_comp(Client()->LatestVersion(), "0" ) != 0) |
255 | { |
256 | char aBuf[64]; |
257 | str_format(aBuf, sizeof(aBuf), Localize("DDNet %s is out!" ), Client()->LatestVersion()); |
258 | TextRender()->TextColor(TextRender()->DefaultTextColor()); |
259 | Ui()->DoLabel(&VersionUpdate, aBuf, 14.0f, TEXTALIGN_MC); |
260 | } |
261 | #endif |
262 | |
263 | if(NewPage != -1) |
264 | { |
265 | m_ShowStart = false; |
266 | SetMenuPage(NewPage); |
267 | } |
268 | } |
269 | |
270 | void CMenus::() |
271 | { |
272 | if(m_ServerProcess.m_Process) |
273 | { |
274 | if(kill_process(process: m_ServerProcess.m_Process)) |
275 | { |
276 | m_ServerProcess.m_Process = INVALID_PROCESS; |
277 | m_ForceRefreshLanPage = true; |
278 | } |
279 | } |
280 | } |
281 | |