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.h"
4
5#include <base/str.h>
6
7#include <engine/shared/config.h>
8#include <engine/storage.h>
9
10#include <game/client/components/menu_background.h>
11#include <game/client/components/tooltips.h>
12#include <game/client/gameclient.h>
13#include <game/client/ui.h>
14#include <game/client/ui_listbox.h>
15#include <game/localization.h>
16
17void CMenus::RenderSettingsGeneral(CUIRect MainView)
18{
19 char aBuf[128 + IO_MAX_PATH_LENGTH];
20 CUIRect Label, Button, Left, Right, Game, ClientSettings;
21 MainView.HSplitTop(Cut: 150.0f, pTop: &Game, pBottom: &ClientSettings);
22
23 // game
24 {
25 // headline
26 Game.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Game);
27 Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Game"), Size: 20.0f, Align: TEXTALIGN_ML);
28 Game.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Game);
29 Game.VSplitMid(pLeft: &Left, pRight: nullptr, Spacing: 20.0f);
30
31 // dynamic camera
32 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
33 const bool IsDyncam = g_Config.m_ClDyncam || g_Config.m_ClMouseFollowfactor > 0;
34 if(DoButton_CheckBox(pId: &g_Config.m_ClDyncam, pText: Localize(pStr: "Dynamic Camera"), Checked: IsDyncam, pRect: &Button))
35 {
36 if(IsDyncam)
37 {
38 g_Config.m_ClDyncam = 0;
39 g_Config.m_ClMouseFollowfactor = 0;
40 }
41 else
42 {
43 g_Config.m_ClDyncam = 1;
44 }
45 }
46
47 // smooth dynamic camera
48 Left.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Left);
49 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
50 if(g_Config.m_ClDyncam)
51 {
52 if(DoButton_CheckBox(pId: &g_Config.m_ClDyncamSmoothness, pText: Localize(pStr: "Smooth Dynamic Camera"), Checked: g_Config.m_ClDyncamSmoothness, pRect: &Button))
53 {
54 if(g_Config.m_ClDyncamSmoothness)
55 {
56 g_Config.m_ClDyncamSmoothness = 0;
57 }
58 else
59 {
60 g_Config.m_ClDyncamSmoothness = 50;
61 g_Config.m_ClDyncamStabilizing = 50;
62 }
63 }
64 }
65
66 // weapon pickup
67 Left.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Left);
68 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
69 if(DoButton_CheckBox(pId: &g_Config.m_ClAutoswitchWeapons, pText: Localize(pStr: "Switch weapon on pickup"), Checked: g_Config.m_ClAutoswitchWeapons, pRect: &Button))
70 g_Config.m_ClAutoswitchWeapons ^= 1;
71
72 // weapon out of ammo autoswitch
73 Left.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Left);
74 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
75 if(DoButton_CheckBox(pId: &g_Config.m_ClAutoswitchWeaponsOutOfAmmo, pText: Localize(pStr: "Switch weapon when out of ammo"), Checked: g_Config.m_ClAutoswitchWeaponsOutOfAmmo, pRect: &Button))
76 g_Config.m_ClAutoswitchWeaponsOutOfAmmo ^= 1;
77 }
78
79 // client
80 {
81 // headline
82 ClientSettings.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &ClientSettings);
83 Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Client"), Size: 20.0f, Align: TEXTALIGN_ML);
84 ClientSettings.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &ClientSettings);
85 ClientSettings.VSplitMid(pLeft: &Left, pRight: &Right, Spacing: 20.0f);
86
87 // skip main menu
88 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
89 if(DoButton_CheckBox(pId: &g_Config.m_ClSkipStartMenu, pText: Localize(pStr: "Skip the main menu"), Checked: g_Config.m_ClSkipStartMenu, pRect: &Button))
90 g_Config.m_ClSkipStartMenu ^= 1;
91
92 Left.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &Left);
93 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
94 str_copy(dst&: aBuf, src: " ");
95 str_append(dst&: aBuf, src: Localize(pStr: "Hz", pContext: "Hertz"));
96 Ui()->DoScrollbarOption(pId: &g_Config.m_ClRefreshRate, pOption: &g_Config.m_ClRefreshRate, pRect: &Button, pStr: Localize(pStr: "Update Rate"), Min: 10, Max: 1000, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_INFINITE | CUi::SCROLLBAR_OPTION_NOCLAMPVALUE | CUi::SCROLLBAR_OPTION_DELAYUPDATE, pSuffix: aBuf);
97 Left.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Left);
98 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
99 static int s_LowerRefreshRate;
100 if(DoButton_CheckBox(pId: &s_LowerRefreshRate, pText: Localize(pStr: "Save power by lowering update rate (higher input latency)"), Checked: g_Config.m_ClRefreshRate <= 480 && g_Config.m_ClRefreshRate != 0, pRect: &Button))
101 g_Config.m_ClRefreshRate = g_Config.m_ClRefreshRate > 480 || g_Config.m_ClRefreshRate == 0 ? 480 : 0;
102
103 CUIRect SettingsButton;
104 Left.HSplitBottom(Cut: 20.0f, pTop: &Left, pBottom: &SettingsButton);
105 Left.HSplitBottom(Cut: 5.0f, pTop: &Left, pBottom: nullptr);
106 static CButtonContainer s_SettingsButtonId;
107 if(DoButton_Menu(pButtonContainer: &s_SettingsButtonId, pText: Localize(pStr: "Settings file"), Checked: 0, pRect: &SettingsButton))
108 {
109 Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, CONFIG_FILE, pBuffer: aBuf, BufferSize: sizeof(aBuf));
110 Client()->ViewFile(pFilename: aBuf);
111 }
112 GameClient()->m_Tooltips.DoToolTip(pId: &s_SettingsButtonId, pNearRect: &SettingsButton, pText: Localize(pStr: "Open the settings file"));
113
114 CUIRect SavesButton;
115 Left.HSplitBottom(Cut: 20.0f, pTop: &Left, pBottom: &SavesButton);
116 Left.HSplitBottom(Cut: 5.0f, pTop: &Left, pBottom: nullptr);
117 static CButtonContainer s_SavesButtonId;
118 if(DoButton_Menu(pButtonContainer: &s_SavesButtonId, pText: Localize(pStr: "Saves file"), Checked: 0, pRect: &SavesButton))
119 {
120 Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: SAVES_FILE, pBuffer: aBuf, BufferSize: sizeof(aBuf));
121 Client()->ViewFile(pFilename: aBuf);
122 }
123 GameClient()->m_Tooltips.DoToolTip(pId: &s_SavesButtonId, pNearRect: &SavesButton, pText: Localize(pStr: "Open the saves file"));
124
125 CUIRect ConfigButton;
126 Left.HSplitBottom(Cut: 20.0f, pTop: &Left, pBottom: &ConfigButton);
127 Left.HSplitBottom(Cut: 5.0f, pTop: &Left, pBottom: nullptr);
128 static CButtonContainer s_ConfigButtonId;
129 if(DoButton_Menu(pButtonContainer: &s_ConfigButtonId, pText: Localize(pStr: "Config directory"), Checked: 0, pRect: &ConfigButton))
130 {
131 Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: "", pBuffer: aBuf, BufferSize: sizeof(aBuf));
132 Client()->ViewFile(pFilename: aBuf);
133 }
134 GameClient()->m_Tooltips.DoToolTip(pId: &s_ConfigButtonId, pNearRect: &ConfigButton, pText: Localize(pStr: "Open the directory that contains the configuration and user files"));
135
136 CUIRect DirectoryButton;
137 Left.HSplitBottom(Cut: 20.0f, pTop: &Left, pBottom: &DirectoryButton);
138 Left.HSplitBottom(Cut: 5.0f, pTop: &Left, pBottom: nullptr);
139 static CButtonContainer s_ThemesButtonId;
140 if(DoButton_Menu(pButtonContainer: &s_ThemesButtonId, pText: Localize(pStr: "Themes directory"), Checked: 0, pRect: &DirectoryButton))
141 {
142 Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: "themes", pBuffer: aBuf, BufferSize: sizeof(aBuf));
143 Storage()->CreateFolder(pFoldername: "themes", Type: IStorage::TYPE_SAVE);
144 Client()->ViewFile(pFilename: aBuf);
145 }
146 GameClient()->m_Tooltips.DoToolTip(pId: &s_ThemesButtonId, pNearRect: &DirectoryButton, pText: Localize(pStr: "Open the directory to add custom themes"));
147
148 Left.HSplitTop(Cut: 20.0f, pTop: nullptr, pBottom: &Left);
149 RenderThemeSelection(MainView: Left);
150
151 // auto demo settings
152 {
153 Right.HSplitTop(Cut: 40.0f, pTop: nullptr, pBottom: &Right);
154 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
155 if(DoButton_CheckBox(pId: &g_Config.m_ClAutoDemoRecord, pText: Localize(pStr: "Automatically record demos"), Checked: g_Config.m_ClAutoDemoRecord, pRect: &Button))
156 g_Config.m_ClAutoDemoRecord ^= 1;
157
158 Right.HSplitTop(Cut: 2 * 20.0f, pTop: &Button, pBottom: &Right);
159 if(g_Config.m_ClAutoDemoRecord)
160 Ui()->DoScrollbarOption(pId: &g_Config.m_ClAutoDemoMax, pOption: &g_Config.m_ClAutoDemoMax, pRect: &Button, pStr: Localize(pStr: "Max demos"), Min: 1, Max: 1000, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_INFINITE | CUi::SCROLLBAR_OPTION_MULTILINE);
161
162 Right.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &Right);
163 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
164 if(DoButton_CheckBox(pId: &g_Config.m_ClAutoScreenshot, pText: Localize(pStr: "Automatically take game over screenshot"), Checked: g_Config.m_ClAutoScreenshot, pRect: &Button))
165 g_Config.m_ClAutoScreenshot ^= 1;
166
167 Right.HSplitTop(Cut: 2 * 20.0f, pTop: &Button, pBottom: &Right);
168 if(g_Config.m_ClAutoScreenshot)
169 Ui()->DoScrollbarOption(pId: &g_Config.m_ClAutoScreenshotMax, pOption: &g_Config.m_ClAutoScreenshotMax, pRect: &Button, pStr: Localize(pStr: "Max Screenshots"), Min: 1, Max: 1000, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_INFINITE | CUi::SCROLLBAR_OPTION_MULTILINE);
170 }
171
172 // auto statboard screenshot
173 {
174 Right.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &Right);
175 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
176 if(DoButton_CheckBox(pId: &g_Config.m_ClAutoStatboardScreenshot, pText: Localize(pStr: "Automatically take statboard screenshot"), Checked: g_Config.m_ClAutoStatboardScreenshot, pRect: &Button))
177 {
178 g_Config.m_ClAutoStatboardScreenshot ^= 1;
179 }
180
181 Right.HSplitTop(Cut: 2 * 20.0f, pTop: &Button, pBottom: &Right);
182 if(g_Config.m_ClAutoStatboardScreenshot)
183 Ui()->DoScrollbarOption(pId: &g_Config.m_ClAutoStatboardScreenshotMax, pOption: &g_Config.m_ClAutoStatboardScreenshotMax, pRect: &Button, pStr: Localize(pStr: "Max Screenshots"), Min: 1, Max: 1000, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_INFINITE | CUi::SCROLLBAR_OPTION_MULTILINE);
184 }
185
186 // auto statboard csv
187 {
188 Right.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &Right);
189 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
190 if(DoButton_CheckBox(pId: &g_Config.m_ClAutoCSV, pText: Localize(pStr: "Automatically create statboard csv"), Checked: g_Config.m_ClAutoCSV, pRect: &Button))
191 {
192 g_Config.m_ClAutoCSV ^= 1;
193 }
194
195 Right.HSplitTop(Cut: 2 * 20.0f, pTop: &Button, pBottom: &Right);
196 if(g_Config.m_ClAutoCSV)
197 Ui()->DoScrollbarOption(pId: &g_Config.m_ClAutoCSVMax, pOption: &g_Config.m_ClAutoCSVMax, pRect: &Button, pStr: Localize(pStr: "Max CSVs"), Min: 1, Max: 1000, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_INFINITE | CUi::SCROLLBAR_OPTION_MULTILINE);
198 }
199 }
200}
201
202void CMenus::RenderThemeSelection(CUIRect MainView)
203{
204 const std::vector<CTheme> &vThemes = GameClient()->m_MenuBackground.GetThemes();
205
206 int SelectedTheme = -1;
207 for(int i = 0; i < (int)vThemes.size(); i++)
208 {
209 if(str_comp(a: vThemes[i].m_Name.c_str(), b: g_Config.m_ClMenuMap) == 0)
210 {
211 SelectedTheme = i;
212 break;
213 }
214 }
215 const int OldSelected = SelectedTheme;
216
217 static CListBox s_ListBox;
218 s_ListBox.DoHeader(pRect: &MainView, pTitle: Localize(pStr: "Theme"), HeaderHeight: 20.0f);
219 s_ListBox.DoStart(RowHeight: 20.0f, NumItems: vThemes.size(), ItemsPerRow: 1, RowsPerScroll: 3, SelectedIndex: SelectedTheme);
220
221 for(int i = 0; i < (int)vThemes.size(); i++)
222 {
223 const CTheme &Theme = vThemes[i];
224 const CListboxItem Item = s_ListBox.DoNextItem(pId: &Theme.m_Name, Selected: i == SelectedTheme);
225
226 if(!Item.m_Visible)
227 continue;
228
229 CUIRect Icon, Label;
230 Item.m_Rect.VSplitLeft(Cut: Item.m_Rect.h * 2.0f, pLeft: &Icon, pRight: &Label);
231
232 // draw icon if it exists
233 if(Theme.m_IconTexture.IsValid())
234 {
235 Icon.VMargin(Cut: 6.0f, pOtherRect: &Icon);
236 Icon.HMargin(Cut: 3.0f, pOtherRect: &Icon);
237 Graphics()->TextureSet(Texture: Theme.m_IconTexture);
238 Graphics()->QuadsBegin();
239 Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f);
240 IGraphics::CQuadItem QuadItem(Icon.x, Icon.y, Icon.w, Icon.h);
241 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
242 Graphics()->QuadsEnd();
243 }
244
245 char aName[128];
246 if(Theme.m_Name.empty())
247 str_copy(dst&: aName, src: "(none)");
248 else if(str_comp(a: Theme.m_Name.c_str(), b: "auto") == 0)
249 str_copy(dst&: aName, src: "(seasons)");
250 else if(str_comp(a: Theme.m_Name.c_str(), b: "rand") == 0)
251 str_copy(dst&: aName, src: "(random)");
252 else if(Theme.m_HasDay && Theme.m_HasNight)
253 str_copy(dst&: aName, src: Theme.m_Name.c_str());
254 else if(Theme.m_HasDay && !Theme.m_HasNight)
255 str_format(buffer: aName, buffer_size: sizeof(aName), format: "%s (day)", Theme.m_Name.c_str());
256 else if(!Theme.m_HasDay && Theme.m_HasNight)
257 str_format(buffer: aName, buffer_size: sizeof(aName), format: "%s (night)", Theme.m_Name.c_str());
258 else // generic
259 str_copy(dst&: aName, src: Theme.m_Name.c_str());
260
261 Ui()->DoLabel(pRect: &Label, pText: aName, Size: 16.0f * CUi::ms_FontmodHeight, Align: TEXTALIGN_ML);
262 }
263
264 SelectedTheme = s_ListBox.DoEnd();
265
266 if(OldSelected != SelectedTheme)
267 {
268 const CTheme &Theme = vThemes[SelectedTheme];
269 str_copy(dst&: g_Config.m_ClMenuMap, src: Theme.m_Name.c_str());
270 GameClient()->m_MenuBackground.LoadMenuBackground(HasDayHint: Theme.m_HasDay, HasNightHint: Theme.m_HasNight);
271 }
272}
273