| 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/dbg.h> |
| 6 | #include <base/fs.h> |
| 7 | #include <base/str.h> |
| 8 | |
| 9 | #include <engine/font_icons.h> |
| 10 | #include <engine/graphics.h> |
| 11 | #include <engine/shared/config.h> |
| 12 | #include <engine/storage.h> |
| 13 | #include <engine/textrender.h> |
| 14 | #include <engine/updater.h> |
| 15 | |
| 16 | #include <game/client/components/background.h> |
| 17 | #include <game/client/components/mapimages.h> |
| 18 | #include <game/client/components/tooltips.h> |
| 19 | #include <game/client/gameclient.h> |
| 20 | #include <game/client/ui.h> |
| 21 | #include <game/client/ui_listbox.h> |
| 22 | #include <game/localization.h> |
| 23 | |
| 24 | #include <algorithm> |
| 25 | |
| 26 | void CMenus::(CUIRect MainView) |
| 27 | { |
| 28 | CUIRect Button, Left, Right, LeftLeft, Label; |
| 29 | |
| 30 | #if defined(CONF_AUTOUPDATE) |
| 31 | CUIRect UpdaterRect; |
| 32 | MainView.HSplitBottom(Cut: 20.0f, pTop: &MainView, pBottom: &UpdaterRect); |
| 33 | MainView.HSplitBottom(Cut: 5.0f, pTop: &MainView, pBottom: nullptr); |
| 34 | #endif |
| 35 | |
| 36 | // demo |
| 37 | CUIRect Demo; |
| 38 | MainView.HSplitTop(Cut: 110.0f, pTop: &Demo, pBottom: &MainView); |
| 39 | Demo.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Demo); |
| 40 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Demo" ), Size: 20.0f, Align: TEXTALIGN_ML); |
| 41 | Label.VSplitMid(pLeft: nullptr, pRight: &Label, Spacing: 20.0f); |
| 42 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Ghost" ), Size: 20.0f, Align: TEXTALIGN_ML); |
| 43 | |
| 44 | Demo.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Demo); |
| 45 | Demo.VSplitMid(pLeft: &Left, pRight: &Right, Spacing: 20.0f); |
| 46 | |
| 47 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
| 48 | if(DoButton_CheckBox(pId: &g_Config.m_ClAutoRaceRecord, pText: Localize(pStr: "Save the best demo of each race" ), Checked: g_Config.m_ClAutoRaceRecord, pRect: &Button)) |
| 49 | { |
| 50 | g_Config.m_ClAutoRaceRecord ^= 1; |
| 51 | } |
| 52 | |
| 53 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
| 54 | if(DoButton_CheckBox(pId: &g_Config.m_ClReplays, pText: Localize(pStr: "Enable replays" ), Checked: g_Config.m_ClReplays, pRect: &Button)) |
| 55 | { |
| 56 | g_Config.m_ClReplays ^= 1; |
| 57 | if(Client()->State() == IClient::STATE_ONLINE) |
| 58 | { |
| 59 | Client()->DemoRecorder_UpdateReplayRecorder(); |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
| 64 | if(g_Config.m_ClReplays) |
| 65 | Ui()->DoScrollbarOption(pId: &g_Config.m_ClReplayLength, pOption: &g_Config.m_ClReplayLength, pRect: &Button, pStr: Localize(pStr: "Default length" ), Min: 10, Max: 600, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_NOCLAMPVALUE); |
| 66 | |
| 67 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
| 68 | if(DoButton_CheckBox(pId: &g_Config.m_ClRaceGhost, pText: Localize(pStr: "Enable ghost" ), Checked: g_Config.m_ClRaceGhost, pRect: &Button)) |
| 69 | { |
| 70 | g_Config.m_ClRaceGhost ^= 1; |
| 71 | } |
| 72 | GameClient()->m_Tooltips.DoToolTip(pId: &g_Config.m_ClRaceGhost, pNearRect: &Button, pText: Localize(pStr: "When you cross the start line, show a ghost tee replicating the movements of your best time" )); |
| 73 | |
| 74 | if(g_Config.m_ClRaceGhost) |
| 75 | { |
| 76 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
| 77 | Button.VSplitMid(pLeft: &LeftLeft, pRight: &Button); |
| 78 | if(DoButton_CheckBox(pId: &g_Config.m_ClRaceShowGhost, pText: Localize(pStr: "Show ghost" ), Checked: g_Config.m_ClRaceShowGhost, pRect: &LeftLeft)) |
| 79 | { |
| 80 | g_Config.m_ClRaceShowGhost ^= 1; |
| 81 | } |
| 82 | Ui()->DoScrollbarOption(pId: &g_Config.m_ClRaceGhostAlpha, pOption: &g_Config.m_ClRaceGhostAlpha, pRect: &Button, pStr: Localize(pStr: "Opacity" ), Min: 0, Max: 100, pScale: &CUi::ms_LinearScrollbarScale, Flags: 0u, pSuffix: "%" ); |
| 83 | |
| 84 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
| 85 | if(DoButton_CheckBox(pId: &g_Config.m_ClRaceSaveGhost, pText: Localize(pStr: "Save ghost" ), Checked: g_Config.m_ClRaceSaveGhost, pRect: &Button)) |
| 86 | { |
| 87 | g_Config.m_ClRaceSaveGhost ^= 1; |
| 88 | } |
| 89 | |
| 90 | if(g_Config.m_ClRaceSaveGhost) |
| 91 | { |
| 92 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
| 93 | if(DoButton_CheckBox(pId: &g_Config.m_ClRaceGhostSaveBest, pText: Localize(pStr: "Only save improvements" ), Checked: g_Config.m_ClRaceGhostSaveBest, pRect: &Button)) |
| 94 | { |
| 95 | g_Config.m_ClRaceGhostSaveBest ^= 1; |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | // gameplay |
| 101 | CUIRect Gameplay; |
| 102 | MainView.HSplitTop(Cut: 190.0f, pTop: &Gameplay, pBottom: &MainView); |
| 103 | Gameplay.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Gameplay); |
| 104 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Gameplay" ), Size: 20.0f, Align: TEXTALIGN_ML); |
| 105 | Gameplay.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Gameplay); |
| 106 | Gameplay.VSplitMid(pLeft: &Left, pRight: &Right, Spacing: 20.0f); |
| 107 | |
| 108 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
| 109 | Ui()->DoScrollbarOption(pId: &g_Config.m_ClOverlayEntities, pOption: &g_Config.m_ClOverlayEntities, pRect: &Button, pStr: Localize(pStr: "Overlay entities" ), Min: 0, Max: 100); |
| 110 | |
| 111 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
| 112 | Button.VSplitMid(pLeft: &LeftLeft, pRight: &Button); |
| 113 | |
| 114 | if(DoButton_CheckBox(pId: &g_Config.m_ClTextEntities, pText: Localize(pStr: "Show text entities" ), Checked: g_Config.m_ClTextEntities, pRect: &LeftLeft)) |
| 115 | g_Config.m_ClTextEntities ^= 1; |
| 116 | |
| 117 | if(g_Config.m_ClTextEntities) |
| 118 | { |
| 119 | if(Ui()->DoScrollbarOption(pId: &g_Config.m_ClTextEntitiesSize, pOption: &g_Config.m_ClTextEntitiesSize, pRect: &Button, pStr: Localize(pStr: "Size" ), Min: 20, Max: 100, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_DELAYUPDATE)) |
| 120 | GameClient()->m_MapImages.SetTextureScale(g_Config.m_ClTextEntitiesSize); |
| 121 | } |
| 122 | |
| 123 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
| 124 | Button.VSplitMid(pLeft: &LeftLeft, pRight: &Button); |
| 125 | |
| 126 | if(DoButton_CheckBox(pId: &g_Config.m_ClShowOthers, pText: Localize(pStr: "Show others" ), Checked: g_Config.m_ClShowOthers == SHOW_OTHERS_ON, pRect: &LeftLeft)) |
| 127 | g_Config.m_ClShowOthers = g_Config.m_ClShowOthers != SHOW_OTHERS_ON ? SHOW_OTHERS_ON : SHOW_OTHERS_OFF; |
| 128 | |
| 129 | Ui()->DoScrollbarOption(pId: &g_Config.m_ClShowOthersAlpha, pOption: &g_Config.m_ClShowOthersAlpha, pRect: &Button, pStr: Localize(pStr: "Opacity" ), Min: 0, Max: 100, pScale: &CUi::ms_LinearScrollbarScale, Flags: 0u, pSuffix: "%" ); |
| 130 | |
| 131 | GameClient()->m_Tooltips.DoToolTip(pId: &g_Config.m_ClShowOthersAlpha, pNearRect: &Button, pText: Localize(pStr: "Adjust the opacity of entities belonging to other teams, such as tees and name plates" )); |
| 132 | |
| 133 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
| 134 | static int s_ShowOwnTeamId = 0; |
| 135 | if(DoButton_CheckBox(pId: &s_ShowOwnTeamId, pText: Localize(pStr: "Show others (own team only)" ), Checked: g_Config.m_ClShowOthers == SHOW_OTHERS_ONLY_TEAM, pRect: &Button)) |
| 136 | { |
| 137 | g_Config.m_ClShowOthers = g_Config.m_ClShowOthers != SHOW_OTHERS_ONLY_TEAM ? SHOW_OTHERS_ONLY_TEAM : SHOW_OTHERS_OFF; |
| 138 | } |
| 139 | |
| 140 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
| 141 | if(DoButton_CheckBox(pId: &g_Config.m_ClShowQuads, pText: Localize(pStr: "Show background quads" ), Checked: g_Config.m_ClShowQuads, pRect: &Button)) |
| 142 | { |
| 143 | g_Config.m_ClShowQuads ^= 1; |
| 144 | } |
| 145 | GameClient()->m_Tooltips.DoToolTip(pId: &g_Config.m_ClShowQuads, pNearRect: &Button, pText: Localize(pStr: "Quads are used for background decoration" )); |
| 146 | |
| 147 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
| 148 | if(Ui()->DoScrollbarOption(pId: &g_Config.m_ClDefaultZoom, pOption: &g_Config.m_ClDefaultZoom, pRect: &Button, pStr: Localize(pStr: "Default zoom" ), Min: 0, Max: 20)) |
| 149 | GameClient()->m_Camera.SetZoom(Target: CCamera::ZoomStepsToValue(Steps: g_Config.m_ClDefaultZoom - 10), Smoothness: g_Config.m_ClSmoothZoomTime, IsUser: true); |
| 150 | |
| 151 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
| 152 | Ui()->DoScrollbarOption(pId: &g_Config.m_ClPredictionMargin, pOption: &g_Config.m_ClPredictionMargin, pRect: &Button, pStr: Localize(pStr: "Prediction margin" ), Min: 1, Max: 300); |
| 153 | |
| 154 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
| 155 | if(DoButton_CheckBox(pId: &g_Config.m_ClPredictEvents, pText: Localize(pStr: "Predict events (experimental)" ), Checked: g_Config.m_ClPredictEvents, pRect: &Button)) |
| 156 | { |
| 157 | g_Config.m_ClPredictEvents ^= 1; |
| 158 | } |
| 159 | |
| 160 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
| 161 | if(DoButton_CheckBox(pId: &g_Config.m_ClAntiPing, pText: Localize(pStr: "AntiPing" ), Checked: g_Config.m_ClAntiPing, pRect: &Button)) |
| 162 | { |
| 163 | g_Config.m_ClAntiPing ^= 1; |
| 164 | } |
| 165 | GameClient()->m_Tooltips.DoToolTip(pId: &g_Config.m_ClAntiPing, pNearRect: &Button, pText: Localize(pStr: "Tries to predict other entities to give a feel of low latency" )); |
| 166 | |
| 167 | if(g_Config.m_ClAntiPing) |
| 168 | { |
| 169 | // AntiPing: players |
| 170 | { |
| 171 | // Base state |
| 172 | { |
| 173 | int Pressed = std::min(a: g_Config.m_ClAntiPingPlayers, b: 2); |
| 174 | const int Prev = Pressed; |
| 175 | DoLine_RadioMenu(View&: Right, pLabel: Localize(pStr: "AntiPing: predict other players" ), |
| 176 | vButtonContainers&: m_vButtonContainersAntiPingPlayers, |
| 177 | vLabels: {Localize(pStr: "Off" , pContext: "AntiPing: predict other players" ), Localize(pStr: "All" , pContext: "AntiPing: predict other players" ), Localize(pStr: "Interact" , pContext: "AntiPing: predict other players" )}, |
| 178 | vValues: {0, 1, 2}, |
| 179 | Value&: Pressed); |
| 180 | if(Pressed != Prev) |
| 181 | g_Config.m_ClAntiPingPlayers = Pressed; |
| 182 | } |
| 183 | |
| 184 | // Interaction state |
| 185 | if(g_Config.m_ClAntiPingPlayers >= 2) |
| 186 | { |
| 187 | bool PrevChecked = g_Config.m_ClAntiPingPlayers == 3; |
| 188 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
| 189 | if(DoButton_CheckBox(pId: &g_Config.m_ClAntiPingPlayers, pText: Localize(pStr: "Always predict frozen players" ), Checked: PrevChecked, pRect: &Button)) |
| 190 | { |
| 191 | g_Config.m_ClAntiPingPlayers = PrevChecked ? 2 : 3; |
| 192 | } |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
| 197 | if(DoButton_CheckBox(pId: &g_Config.m_ClAntiPingWeapons, pText: Localize(pStr: "AntiPing: predict weapons" ), Checked: g_Config.m_ClAntiPingWeapons, pRect: &Button)) |
| 198 | { |
| 199 | g_Config.m_ClAntiPingWeapons ^= 1; |
| 200 | } |
| 201 | |
| 202 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
| 203 | if(DoButton_CheckBox(pId: &g_Config.m_ClAntiPingGrenade, pText: Localize(pStr: "AntiPing: predict grenade paths" ), Checked: g_Config.m_ClAntiPingGrenade, pRect: &Button)) |
| 204 | { |
| 205 | g_Config.m_ClAntiPingGrenade ^= 1; |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | CUIRect Background, Miscellaneous; |
| 210 | MainView.VSplitMid(pLeft: &Background, pRight: &Miscellaneous, Spacing: 20.0f); |
| 211 | |
| 212 | // background |
| 213 | Background.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Background); |
| 214 | Background.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Background); |
| 215 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Background" ), Size: 20.0f, Align: TEXTALIGN_ML); |
| 216 | |
| 217 | ColorRGBA GreyDefault(0.5f, 0.5f, 0.5f, 1); |
| 218 | |
| 219 | static CButtonContainer s_ResetId1; |
| 220 | DoLine_ColorPicker(pResetId: &s_ResetId1, LineSize: 25.0f, LabelSize: 13.0f, BottomMargin: 5.0f, pMainRect: &Background, pText: Localize(pStr: "Regular background color" ), pColorValue: &g_Config.m_ClBackgroundColor, DefaultColor: GreyDefault, CheckBoxSpacing: false); |
| 221 | |
| 222 | static CButtonContainer s_ResetId2; |
| 223 | DoLine_ColorPicker(pResetId: &s_ResetId2, LineSize: 25.0f, LabelSize: 13.0f, BottomMargin: 5.0f, pMainRect: &Background, pText: Localize(pStr: "Entities background color" ), pColorValue: &g_Config.m_ClBackgroundEntitiesColor, DefaultColor: GreyDefault, CheckBoxSpacing: false); |
| 224 | |
| 225 | CUIRect EditBox, ReloadButton; |
| 226 | Background.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &Background); |
| 227 | Background.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &Background); |
| 228 | Label.VSplitLeft(Cut: 100.0f, pLeft: &Label, pRight: &EditBox); |
| 229 | EditBox.VSplitRight(Cut: 60.0f, pLeft: &EditBox, pRight: &Button); |
| 230 | Button.VSplitMid(pLeft: &ReloadButton, pRight: &Button, Spacing: 5.0f); |
| 231 | EditBox.VSplitRight(Cut: 5.0f, pLeft: &EditBox, pRight: nullptr); |
| 232 | |
| 233 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Map" ), Size: 14.0f, Align: TEXTALIGN_ML); |
| 234 | |
| 235 | static CLineInput s_BackgroundEntitiesInput(g_Config.m_ClBackgroundEntities, sizeof(g_Config.m_ClBackgroundEntities)); |
| 236 | Ui()->DoEditBox(pLineInput: &s_BackgroundEntitiesInput, pRect: &EditBox, FontSize: 14.0f); |
| 237 | |
| 238 | static CButtonContainer s_BackgroundEntitiesMapPicker, s_BackgroundEntitiesReload; |
| 239 | |
| 240 | if(Ui()->DoButton_FontIcon(pButtonContainer: &s_BackgroundEntitiesReload, pText: FontIcon::ARROW_ROTATE_RIGHT, Checked: 0, pRect: &ReloadButton, Flags: BUTTONFLAG_LEFT)) |
| 241 | { |
| 242 | GameClient()->m_Background.LoadBackground(); |
| 243 | } |
| 244 | |
| 245 | if(Ui()->DoButton_FontIcon(pButtonContainer: &s_BackgroundEntitiesMapPicker, pText: FontIcon::FOLDER, Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT)) |
| 246 | { |
| 247 | static SPopupMenuId ; |
| 248 | static CPopupMapPickerContext ; |
| 249 | s_PopupMapPickerContext.m_pMenus = this; |
| 250 | s_PopupMapPickerContext.MapListPopulate(); |
| 251 | Ui()->DoPopupMenu(pId: &s_PopupMapPickerId, X: Ui()->MouseX(), Y: Ui()->MouseY(), Width: 300.0f, Height: 250.0f, pContext: &s_PopupMapPickerContext, pfnFunc: PopupMapPicker); |
| 252 | } |
| 253 | |
| 254 | Background.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Background); |
| 255 | const bool UseCurrentMap = str_comp(a: g_Config.m_ClBackgroundEntities, CURRENT_MAP) == 0; |
| 256 | static int s_UseCurrentMapId = 0; |
| 257 | if(DoButton_CheckBox(pId: &s_UseCurrentMapId, pText: Localize(pStr: "Use current map as background" ), Checked: UseCurrentMap, pRect: &Button)) |
| 258 | { |
| 259 | if(UseCurrentMap) |
| 260 | g_Config.m_ClBackgroundEntities[0] = '\0'; |
| 261 | else |
| 262 | str_copy(dst&: g_Config.m_ClBackgroundEntities, CURRENT_MAP); |
| 263 | GameClient()->m_Background.LoadBackground(); |
| 264 | } |
| 265 | |
| 266 | Background.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Background); |
| 267 | if(DoButton_CheckBox(pId: &g_Config.m_ClBackgroundShowTilesLayers, pText: Localize(pStr: "Show tiles layers from BG map" ), Checked: g_Config.m_ClBackgroundShowTilesLayers, pRect: &Button)) |
| 268 | g_Config.m_ClBackgroundShowTilesLayers ^= 1; |
| 269 | |
| 270 | // miscellaneous |
| 271 | Miscellaneous.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Miscellaneous); |
| 272 | Miscellaneous.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Miscellaneous); |
| 273 | |
| 274 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Miscellaneous" ), Size: 20.0f, Align: TEXTALIGN_ML); |
| 275 | |
| 276 | static CButtonContainer s_ButtonTimeout; |
| 277 | Miscellaneous.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Miscellaneous); |
| 278 | if(DoButton_Menu(pButtonContainer: &s_ButtonTimeout, pText: Localize(pStr: "New random timeout code" ), Checked: 0, pRect: &Button)) |
| 279 | { |
| 280 | Client()->GenerateTimeoutSeed(); |
| 281 | } |
| 282 | |
| 283 | Miscellaneous.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Miscellaneous); |
| 284 | Miscellaneous.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &Miscellaneous); |
| 285 | Miscellaneous.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &Miscellaneous); |
| 286 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Run on join" ), Size: 14.0f, Align: TEXTALIGN_ML); |
| 287 | Miscellaneous.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Miscellaneous); |
| 288 | static CLineInput s_RunOnJoinInput(g_Config.m_ClRunOnJoin, sizeof(g_Config.m_ClRunOnJoin)); |
| 289 | s_RunOnJoinInput.SetEmptyText(Localize(pStr: "Chat command (e.g. showall 1)" )); |
| 290 | Ui()->DoEditBox(pLineInput: &s_RunOnJoinInput, pRect: &Button, FontSize: 14.0f); |
| 291 | |
| 292 | #if defined(CONF_FAMILY_WINDOWS) |
| 293 | static CButtonContainer s_ButtonUnregisterShell; |
| 294 | Miscellaneous.HSplitTop(10.0f, nullptr, &Miscellaneous); |
| 295 | Miscellaneous.HSplitTop(20.0f, &Button, &Miscellaneous); |
| 296 | if(DoButton_Menu(&s_ButtonUnregisterShell, Localize("Unregister protocol and file extensions" ), 0, &Button)) |
| 297 | { |
| 298 | Client()->ShellUnregister(); |
| 299 | } |
| 300 | #endif |
| 301 | |
| 302 | // Updater |
| 303 | #if defined(CONF_AUTOUPDATE) |
| 304 | { |
| 305 | bool NeedUpdate = str_comp(a: Client()->LatestVersion(), b: "0" ); |
| 306 | IUpdater::EUpdaterState State = Updater()->GetCurrentState(); |
| 307 | |
| 308 | // Update Button |
| 309 | char aBuf[256]; |
| 310 | if(NeedUpdate && State <= IUpdater::CLEAN) |
| 311 | { |
| 312 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "DDNet %s is available:" ), Client()->LatestVersion()); |
| 313 | UpdaterRect.VSplitLeft(Cut: TextRender()->TextWidth(Size: 14.0f, pText: aBuf, StrLength: -1, LineWidth: -1.0f) + 10.0f, pLeft: &UpdaterRect, pRight: &Button); |
| 314 | Button.VSplitLeft(Cut: 100.0f, pLeft: &Button, pRight: nullptr); |
| 315 | static CButtonContainer s_ButtonUpdate; |
| 316 | if(DoButton_Menu(pButtonContainer: &s_ButtonUpdate, pText: Localize(pStr: "Update now" ), Checked: 0, pRect: &Button)) |
| 317 | { |
| 318 | Updater()->InitiateUpdate(); |
| 319 | } |
| 320 | } |
| 321 | else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART) |
| 322 | str_copy(dst&: aBuf, src: Localize(pStr: "Updating…" )); |
| 323 | else if(State == IUpdater::NEED_RESTART) |
| 324 | { |
| 325 | str_copy(dst&: aBuf, src: Localize(pStr: "DDNet Client updated!" )); |
| 326 | m_NeedRestartUpdate = true; |
| 327 | } |
| 328 | else |
| 329 | { |
| 330 | str_copy(dst&: aBuf, src: Localize(pStr: "No updates available" )); |
| 331 | UpdaterRect.VSplitLeft(Cut: TextRender()->TextWidth(Size: 14.0f, pText: aBuf, StrLength: -1, LineWidth: -1.0f) + 10.0f, pLeft: &UpdaterRect, pRight: &Button); |
| 332 | Button.VSplitLeft(Cut: 100.0f, pLeft: &Button, pRight: nullptr); |
| 333 | static CButtonContainer s_ButtonUpdate; |
| 334 | if(DoButton_Menu(pButtonContainer: &s_ButtonUpdate, pText: Localize(pStr: "Check now" ), Checked: 0, pRect: &Button)) |
| 335 | { |
| 336 | Client()->RequestDDNetInfo(); |
| 337 | } |
| 338 | } |
| 339 | Ui()->DoLabel(pRect: &UpdaterRect, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML); |
| 340 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
| 341 | } |
| 342 | #endif |
| 343 | } |
| 344 | |
| 345 | CUi::EPopupMenuFunctionResult CMenus::(void *pContext, CUIRect View, bool Active) |
| 346 | { |
| 347 | CPopupMapPickerContext * = static_cast<CPopupMapPickerContext *>(pContext); |
| 348 | CMenus * = pPopupContext->m_pMenus; |
| 349 | |
| 350 | static CListBox s_ListBox; |
| 351 | s_ListBox.SetActive(Active); |
| 352 | s_ListBox.DoStart(RowHeight: 20.0f, NumItems: pPopupContext->m_vMaps.size(), ItemsPerRow: 1, RowsPerScroll: 3, SelectedIndex: -1, pRect: &View, Background: false); |
| 353 | |
| 354 | int MapIndex = 0; |
| 355 | for(auto &Map : pPopupContext->m_vMaps) |
| 356 | { |
| 357 | MapIndex++; |
| 358 | const CListboxItem Item = s_ListBox.DoNextItem(pId: &Map, Selected: MapIndex == pPopupContext->m_Selection); |
| 359 | if(!Item.m_Visible) |
| 360 | continue; |
| 361 | |
| 362 | CUIRect Label, Icon; |
| 363 | Item.m_Rect.VSplitLeft(Cut: 20.0f, pLeft: &Icon, pRight: &Label); |
| 364 | |
| 365 | char aLabelText[IO_MAX_PATH_LENGTH]; |
| 366 | str_copy(dst&: aLabelText, src: Map.m_aFilename); |
| 367 | if(Map.m_IsDirectory) |
| 368 | str_append(dst&: aLabelText, src: "/" ); |
| 369 | |
| 370 | const char *pIconType; |
| 371 | if(!Map.m_IsDirectory) |
| 372 | { |
| 373 | pIconType = FontIcon::MAP; |
| 374 | } |
| 375 | else |
| 376 | { |
| 377 | if(!str_comp(a: Map.m_aFilename, b: ".." )) |
| 378 | pIconType = FontIcon::FOLDER_TREE; |
| 379 | else |
| 380 | pIconType = FontIcon::FOLDER; |
| 381 | } |
| 382 | |
| 383 | pMenus->TextRender()->SetFontPreset(EFontPreset::ICON_FONT); |
| 384 | pMenus->TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING); |
| 385 | pMenus->Ui()->DoLabel(pRect: &Icon, pText: pIconType, Size: 12.0f, Align: TEXTALIGN_ML); |
| 386 | pMenus->TextRender()->SetRenderFlags(0); |
| 387 | pMenus->TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); |
| 388 | |
| 389 | pMenus->Ui()->DoLabel(pRect: &Label, pText: aLabelText, Size: 10.0f, Align: TEXTALIGN_ML); |
| 390 | } |
| 391 | |
| 392 | const int NewSelected = s_ListBox.DoEnd(); |
| 393 | pPopupContext->m_Selection = NewSelected >= 0 ? NewSelected : -1; |
| 394 | if(s_ListBox.WasItemSelected() || s_ListBox.WasItemActivated()) |
| 395 | { |
| 396 | const CMapListItem &SelectedItem = pPopupContext->m_vMaps[pPopupContext->m_Selection]; |
| 397 | |
| 398 | if(SelectedItem.m_IsDirectory) |
| 399 | { |
| 400 | if(!str_comp(a: SelectedItem.m_aFilename, b: ".." )) |
| 401 | { |
| 402 | dbg_assert(fs_parent_dir(pPopupContext->m_aCurrentMapFolder) == 0, "Parent folder item selected but there is no parent folder" ); |
| 403 | } |
| 404 | else |
| 405 | { |
| 406 | str_append(dst&: pPopupContext->m_aCurrentMapFolder, src: "/" ); |
| 407 | str_append(dst&: pPopupContext->m_aCurrentMapFolder, src: SelectedItem.m_aFilename); |
| 408 | } |
| 409 | pPopupContext->MapListPopulate(); |
| 410 | } |
| 411 | else |
| 412 | { |
| 413 | str_format(buffer: g_Config.m_ClBackgroundEntities, buffer_size: sizeof(g_Config.m_ClBackgroundEntities), format: "%s/%s" , pPopupContext->m_aCurrentMapFolder, SelectedItem.m_aFilename); |
| 414 | pMenus->GameClient()->m_Background.LoadBackground(); |
| 415 | return CUi::POPUP_CLOSE_CURRENT; |
| 416 | } |
| 417 | } |
| 418 | |
| 419 | return CUi::POPUP_KEEP_OPEN; |
| 420 | } |
| 421 | |
| 422 | void CMenus::CPopupMapPickerContext::() |
| 423 | { |
| 424 | m_vMaps.clear(); |
| 425 | char aTemp[IO_MAX_PATH_LENGTH]; |
| 426 | str_format(buffer: aTemp, buffer_size: sizeof(aTemp), format: "maps/%s" , m_aCurrentMapFolder); |
| 427 | m_pMenus->Storage()->ListDirectoryInfo(Type: IStorage::TYPE_ALL, pPath: aTemp, pfnCallback: MapListFetchCallback, pUser: this); |
| 428 | std::stable_sort(first: m_vMaps.begin(), last: m_vMaps.end(), comp: CompareFilenameAscending); |
| 429 | m_Selection = -1; |
| 430 | } |
| 431 | |
| 432 | int CMenus::CPopupMapPickerContext::(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser) |
| 433 | { |
| 434 | CPopupMapPickerContext *pRealUser = (CPopupMapPickerContext *)pUser; |
| 435 | if((!IsDir && !str_endswith(str: pInfo->m_pName, suffix: ".map" )) || !str_comp(a: pInfo->m_pName, b: "." ) || (!str_comp(a: pInfo->m_pName, b: ".." ) && (!str_comp(a: pRealUser->m_aCurrentMapFolder, b: "" )))) |
| 436 | return 0; |
| 437 | |
| 438 | CMapListItem Item; |
| 439 | str_copy(dst&: Item.m_aFilename, src: pInfo->m_pName); |
| 440 | Item.m_IsDirectory = IsDir; |
| 441 | |
| 442 | pRealUser->m_vMaps.emplace_back(args&: Item); |
| 443 | |
| 444 | return 0; |
| 445 | } |
| 446 | |