1 /** @file hu_menu.cpp  Menu widget stuff, episode selection and such.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2005-2015 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA</small>
19  */
20 
21 #include "common.h"
22 #include "hu_menu.h"
23 
24 #include <cstdlib>
25 #include <cctype>
26 #include <cmath>
27 #include <cstdio>
28 #include <cstring>
29 #include <QMap>
30 #include <QtAlgorithms>
31 #include <de/memory.h>
32 #include <de/RecordValue>
33 #include "g_common.h"
34 #include "g_controls.h"
35 #include "g_defs.h"
36 #include "gamesession.h"
37 #include "hu_msg.h"
38 #include "hu_stuff.h"
39 #include "m_argv.h"
40 #include "m_ctrl.h"
41 #include "p_savedef.h"
42 #include "player.h"
43 #include "r_common.h"
44 #include "saveslots.h"
45 #include "x_hair.h"
46 
47 #include "menu/page.h"
48 #include "menu/widgets/coloreditwidget.h"
49 #include "menu/widgets/cvarcoloreditwidget.h"
50 #include "menu/widgets/cvarinlinelistwidget.h"
51 #include "menu/widgets/cvarlineeditwidget.h"
52 #include "menu/widgets/cvarsliderwidget.h"
53 #include "menu/widgets/cvartextualsliderwidget.h"
54 #include "menu/widgets/cvartogglewidget.h"
55 #include "menu/widgets/inputbindingwidget.h"
56 #include "menu/widgets/labelwidget.h"
57 #include "menu/widgets/mobjpreviewwidget.h"
58 #include "menu/widgets/rectwidget.h"
59 #include "menu/widgets/sliderwidget.h"
60 
61 using namespace de;
62 
63 namespace common {
64 
65 using namespace common::menu;
66 
67 /// Original game line height for pages that employ the fixed layout (in 320x200 pixels).
68 #if __JDOOM__
69 #  define FIXED_LINE_HEIGHT (15+1)
70 #else
71 #  define FIXED_LINE_HEIGHT (19+1)
72 #endif
73 
74 void Hu_MenuActivatePlayerSetup(Page &page);
75 
76 void Hu_MenuActionSetActivePage(Widget &wi, Widget::Action action);
77 void Hu_MenuActionInitNewGame(Widget &wi, Widget::Action action);
78 
79 void Hu_MenuSelectLoadGame(Widget &wi, Widget::Action action);
80 void Hu_MenuSelectSaveGame(Widget &wi, Widget::Action action);
81 void Hu_MenuSelectJoinGame(Widget &wi, Widget::Action action);
82 
83 #if __JDOOM__ || __JHERETIC__ || __JHEXEN__
84 void Hu_MenuSelectHelp(Widget &wi, Widget::Action action);
85 #endif
86 void Hu_MenuSelectControlPanelLink(Widget &wi, Widget::Action action);
87 
88 void Hu_MenuSelectSingleplayer(Widget &wi, Widget::Action action);
89 //void Hu_MenuSelectMultiplayer(Widget &wi, Widget::Action action);
90 void Hu_MenuSelectEpisode(Widget &wi, Widget::Action action);
91 #if __JDOOM__ || __JHERETIC__
92 void Hu_MenuActivateNotSharewareEpisode(Widget &wi, Widget::Action action);
93 #endif
94 #if __JHEXEN__
95 void Hu_MenuFocusOnPlayerClass(Widget &wi, Widget::Action action);
96 void Hu_MenuSelectPlayerClass(Widget &wi, Widget::Action action);
97 #endif
98 void Hu_MenuFocusSkillMode(Widget &wi, Widget::Action action);
99 void Hu_MenuSelectLoadSlot(Widget &wi, Widget::Action action);
100 void Hu_MenuSelectQuitGame(Widget &wi, Widget::Action action);
101 void Hu_MenuSelectEndGame(Widget &wi, Widget::Action action);
102 void Hu_MenuSelectAcceptPlayerSetup(Widget &wi, Widget::Action action);
103 
104 void Hu_MenuSelectSaveSlot(Widget &wi, Widget::Action action);
105 
106 void Hu_MenuChangeWeaponPriority(Widget &wi, Widget::Action action);
107 #if __JHEXEN__
108 void Hu_MenuSelectPlayerSetupPlayerClass(Widget &wi, Widget::Action action);
109 #endif
110 void Hu_MenuSelectPlayerColor(Widget &wi, Widget::Action action);
111 
112 #if __JHEXEN__
113 void Hu_MenuPlayerClassBackgroundTicker(Widget &wi);
114 void Hu_MenuPlayerClassPreviewTicker(Widget &wi);
115 #endif
116 
117 #if __JHERETIC__ || __JHEXEN__
118 void Hu_MenuDrawMainPage(Page const &page, Vector2i const &origin);
119 #endif
120 
121 //void Hu_MenuDrawGameTypePage(Page const &page, Vector2i const &origin);
122 void Hu_MenuDrawSkillPage(Page const &page, Vector2i const &origin);
123 #if __JHEXEN__
124 void Hu_MenuDrawPlayerClassPage(Page const &page, Vector2i const &origin);
125 #endif
126 void Hu_MenuDrawEpisodePage(Page const &page, Vector2i const &origin);
127 void Hu_MenuDrawOptionsPage(Page const &page, Vector2i const &origin);
128 void Hu_MenuDrawLoadGamePage(Page const &page, Vector2i const &origin);
129 void Hu_MenuDrawSaveGamePage(Page const &page, Vector2i const &origin);
130 void Hu_MenuDrawMultiplayerPage(Page const &page, Vector2i const &origin);
131 void Hu_MenuDrawPlayerSetupPage(Page const &page, Vector2i const &origin);
132 
133 int Hu_MenuColorWidgetCmdResponder(Page &page, menucommand_e cmd);
134 int Hu_MenuSkipPreviousPageIfSkippingEpisodeSelection(Page &page, menucommand_e cmd);
135 
136 void Hu_MenuSaveSlotEdit(Widget &wi, Widget::Action action);
137 
138 void Hu_MenuActivateColorWidget(Widget &wi, Widget::Action action);
139 void Hu_MenuUpdateColorWidgetColor(Widget &wi, Widget::Action action);
140 
141 static void Hu_MenuInitNewGame(bool confirmed);
142 
143 static void initAllPages();
144 static void destroyAllPages();
145 
146 static void Hu_MenuUpdateCursorState();
147 
148 static bool Hu_MenuHasCursorRotation(Widget *wi);
149 
150 int menuTime;
151 dd_bool menuNominatingQuickSaveSlot;
152 
153 static Page *currentPage;
154 static bool menuActive;
155 
156 static float mnAlpha; // Alpha level for the entire menu.
157 static float mnTargetAlpha; // Target alpha for the entire UI.
158 
159 static skillmode_t mnSkillmode = SM_MEDIUM;
160 static String mnEpisode;
161 #if __JHEXEN__
162 static int mnPlrClass = PCLASS_FIGHTER;
163 #endif
164 
165 static int frame; // Used by any graphic animations that need to be pumped.
166 
167 static bool colorWidgetActive;
168 
169 // Present cursor state.
170 struct Cursor
171 {
172     bool hasRotation = false;
173     float angle      = 0;
174     int animCounter  = 0;
175     int animFrame    = 0;
176 };
177 static Cursor cursor;
178 
179 static patchid_t pMainTitle;
180 #if __JDOOM__ || __JDOOM64__
181 static patchid_t pNewGame;
182 static patchid_t pSkill;
183 static patchid_t pEpisode;
184 static patchid_t pNGame;
185 static patchid_t pOptions;
186 static patchid_t pLoadGame;
187 static patchid_t pSaveGame;
188 static patchid_t pReadThis;
189 static patchid_t pQuitGame;
190 static patchid_t pOptionsTitle;
191 
192 static patchid_t pSkillModeNames[NUM_SKILL_MODES];
193 #endif
194 
195 #if __JHEXEN__
196 static patchid_t pPlayerClassBG[3];
197 static patchid_t pBullWithFire[8];
198 #endif
199 
200 #if __JHERETIC__
201 static patchid_t pRotatingSkull[18];
202 #endif
203 
204 static patchid_t pCursors[MENU_CURSOR_FRAMECOUNT];
205 
206 static bool inited;
207 
208 typedef QMap<String, Page *> Pages;
209 static Pages pages;
210 
chooseCloseMethod()211 static menucommand_e chooseCloseMethod()
212 {
213     // If we aren't using a transition then we can close normally and allow our
214     // own menu fade-out animation to be used instead.
215     return Con_GetInteger("con-transition-tics") == 0? MCMD_CLOSE : MCMD_CLOSEFAST;
216 }
217 
Hu_MenuHasPage(String name)218 bool Hu_MenuHasPage(String name)
219 {
220     if(!name.isEmpty())
221     {
222         return pages.contains(name.toLower());
223     }
224     return false;
225 }
226 
Hu_MenuPage(String name)227 Page &Hu_MenuPage(String name)
228 {
229     if(!name.isEmpty())
230     {
231         Pages::iterator found = pages.find(name.toLower());
232         if(found != pages.end())
233         {
234             return *found.value();
235         }
236     }
237     /// @throw Error No Page exists with the name specified.
238     throw Error("Hu_MenuPage", "Unknown page '" + name + "'");
239 }
240 
241 /// @todo Make this state an object property flag.
242 /// @return  @c true if the rotation of a cursor on this object should be animated.
Hu_MenuHasCursorRotation(Widget * wi)243 static bool Hu_MenuHasCursorRotation(Widget *wi)
244 {
245     DENG2_ASSERT(wi != 0);
246     return (!wi->isDisabled() && (is<InlineListWidget>(wi) || is<SliderWidget>(wi)));
247 }
248 
249 /// To be called to re-evaluate the state of the cursor (e.g., when focus changes).
Hu_MenuUpdateCursorState()250 static void Hu_MenuUpdateCursorState()
251 {
252     if(menuActive)
253     {
254         Page *page = colorWidgetActive? Hu_MenuPagePtr("ColorWidget") : Hu_MenuPagePtr();
255         if(Widget *wi = page->focusWidget())
256         {
257             cursor.hasRotation = Hu_MenuHasCursorRotation(wi);
258             return;
259         }
260     }
261     cursor.hasRotation = false;
262 }
263 
Hu_MenuLoadResources()264 static void Hu_MenuLoadResources()
265 {
266     char buf[9];
267 
268 #if __JDOOM__ || __JDOOM64__
269     pMainTitle = R_DeclarePatch("M_DOOM");
270 #elif __JHERETIC__ || __JHEXEN__
271     pMainTitle = R_DeclarePatch("M_HTIC");
272 #endif
273 
274 #if __JDOOM__ || __JDOOM64__
275     pNewGame  = R_DeclarePatch("M_NEWG");
276     pSkill    = R_DeclarePatch("M_SKILL");
277     pEpisode  = R_DeclarePatch("M_EPISOD");
278     pNGame    = R_DeclarePatch("M_NGAME");
279     pOptions  = R_DeclarePatch("M_OPTION");
280     pLoadGame = R_DeclarePatch("M_LOADG");
281     pSaveGame = R_DeclarePatch("M_SAVEG");
282     pReadThis = R_DeclarePatch("M_RDTHIS");
283     pQuitGame = R_DeclarePatch("M_QUITG");
284     pOptionsTitle = R_DeclarePatch("M_OPTTTL");
285 #endif
286 
287 #if __JDOOM__ || __JDOOM64__
288     pSkillModeNames[SM_BABY]      = R_DeclarePatch("M_JKILL");
289     pSkillModeNames[SM_EASY]      = R_DeclarePatch("M_ROUGH");
290     pSkillModeNames[SM_MEDIUM]    = R_DeclarePatch("M_HURT");
291     pSkillModeNames[SM_HARD]      = R_DeclarePatch("M_ULTRA");
292 #  if __JDOOM__
293     pSkillModeNames[SM_NIGHTMARE] = R_DeclarePatch("M_NMARE");
294 #  endif
295 #endif
296 
297 #if __JHERETIC__
298     for(int i = 0; i < 18; ++i)
299     {
300         dd_snprintf(buf, 9, "M_SKL%02d", i);
301         pRotatingSkull[i] = R_DeclarePatch(buf);
302     }
303 #endif
304 
305 #if __JHEXEN__
306     for(int i = 0; i < 7; ++i)
307     {
308         dd_snprintf(buf, 9, "FBUL%c0", 'A'+i);
309         pBullWithFire[i] = R_DeclarePatch(buf);
310     }
311 
312     pPlayerClassBG[0] = R_DeclarePatch("M_FBOX");
313     pPlayerClassBG[1] = R_DeclarePatch("M_CBOX");
314     pPlayerClassBG[2] = R_DeclarePatch("M_MBOX");
315 #endif
316 
317     for(int i = 0; i < MENU_CURSOR_FRAMECOUNT; ++i)
318     {
319 #if __JDOOM__ || __JDOOM64__
320         dd_snprintf(buf, 9, "M_SKULL%d", i+1);
321 #else
322         dd_snprintf(buf, 9, "M_SLCTR%d", i+1);
323 #endif
324         pCursors[i] = R_DeclarePatch(buf);
325     }
326 }
327 
Hu_MenuInitColorWidgetPage()328 void Hu_MenuInitColorWidgetPage()
329 {
330 #if __JHERETIC__ || __JHEXEN__
331     Vector2i const origin(98, 60);
332 #else
333     Vector2i const origin(124, 60);
334 #endif
335 
336     Page *page = Hu_MenuAddPage(new Page("ColorWidget", origin, Page::NoScroll, NULL, Hu_MenuColorWidgetCmdResponder));
337     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTA));
338 
339     page->addWidget(new ColorEditWidget(Vector4f(), true))
340             .setPreviewDimensions(Vector2i(SCREENHEIGHT / 7, SCREENHEIGHT / 7))
341             .setFlags(Widget::Id0 | Widget::NoFocus);
342 
343     page->addWidget(new LabelWidget("Red"));
344 
345     page->addWidget(new SliderWidget(0.0f, 1.0f, .05f))
346             .setFlags(Widget::Id1)
347             .setShortcut('r')
348             .setUserValue2(int(CR))
349             .setAction(Widget::Modified,    Hu_MenuUpdateColorWidgetColor)
350             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
351 
352     page->addWidget(new LabelWidget("Green"));
353     page->addWidget(new SliderWidget(0.0f, 1.0f, .05f))
354             .setFlags(Widget::Id2)
355             .setShortcut('g')
356             .setUserValue2(int(CG))
357             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction)
358             .setAction(Widget::Modified,    Hu_MenuUpdateColorWidgetColor);
359 
360     page->addWidget(new LabelWidget("Blue"));
361     page->addWidget(new SliderWidget(0.0f, 1.0f, 0.05f))
362             .setFlags(Widget::Id3)
363             .setShortcut('b')
364             .setUserValue2(int(CB))
365             .setAction(Widget::Modified,    Hu_MenuUpdateColorWidgetColor)
366             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
367 
368     page->addWidget(new LabelWidget("Opacity")).setFlags(Widget::Id4);
369     page->addWidget(new SliderWidget(0.0f, 1.0f, 0.05f))
370             .setFlags(Widget::Id5)
371             .setShortcut('o')
372             .setUserValue2(int(CA))
373             .setAction(Widget::Modified,    Hu_MenuUpdateColorWidgetColor)
374             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
375 }
376 
Hu_MenuInitMainPage()377 void Hu_MenuInitMainPage()
378 {
379 #if __JHEXEN__ || __JHERETIC__
380     Vector2i origin(110, 56);
381 #else
382     Vector2i origin(97, 64);
383 #endif
384 
385 #if __JDOOM__
386     if(gameModeBits & GM_ANY_DOOM2)
387     {
388         origin.y += 8;
389     }
390 #endif
391 
392 #if __JDOOM__ || __JDOOM64__
393     Page *page = Hu_MenuAddPage(new Page("Main", origin, Page::FixedLayout | Page::NoScroll));
394 #else
395     Page *page = Hu_MenuAddPage(new Page("Main", origin, Page::FixedLayout | Page::NoScroll, Hu_MenuDrawMainPage));
396 #endif
397     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTB));
398 
399     int y = 0;
400 
401 #if __JDOOM__ || __JDOOM64__
402     page->addWidget(new LabelWidget("", &pMainTitle))
403             .setFixedOrigin(Vector2i(-3, -70));
404 #endif
405 
406     page->addWidget(new ButtonWidget)
407 #if defined(__JDOOM__) && !defined(__JDOOM64__)
408             .setPatch(pNGame)
409 #else
410             .setText("New Game")
411 #endif
412             .setFixedY(y)
413             .setShortcut('n')
414             .setFont(MENU_FONT1)
415             //.setUserValue(String("GameType"))
416             .setAction(Widget::Deactivated, Hu_MenuSelectSingleplayer)
417             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
418 
419     y += FIXED_LINE_HEIGHT;
420 
421     page->addWidget(new ButtonWidget)
422 #if defined(__JDOOM__) && !defined(__JDOOM64__)
423             .setPatch(pOptions)
424 #else
425             .setText("Options")
426 #endif
427             .setFixedY(y)
428             .setShortcut('o')
429             .setFont(MENU_FONT1)
430             .setUserValue(String("Options"))
431             .setAction(Widget::Deactivated, Hu_MenuActionSetActivePage)
432             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
433 
434     y += FIXED_LINE_HEIGHT;
435 
436 #if __JDOOM__ || __JDOOM64__
437     page->addWidget(new ButtonWidget)
438 # if __JDOOM64__
439             .setText("Load Game")
440 # else
441             .setPatch(pLoadGame)
442 # endif
443             .setFixedY(y)
444             .setShortcut('l')
445             .setFont(MENU_FONT1)
446             .setAction(Widget::Deactivated, Hu_MenuSelectLoadGame)
447             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
448 
449     y += FIXED_LINE_HEIGHT;
450 
451     page->addWidget(new ButtonWidget)
452 # if __JDOOM64__
453             .setText("Save Game")
454 # else
455             .setPatch(pSaveGame)
456 # endif
457             .setFixedY(y)
458             .setShortcut('s')
459             .setFont(MENU_FONT1)
460             .setAction(Widget::Deactivated, Hu_MenuSelectSaveGame)
461             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
462 
463     y += FIXED_LINE_HEIGHT;
464 
465 #else
466     page->addWidget(new ButtonWidget("Game Files"))
467             .setFixedY(y)
468             .setShortcut('f')
469             .setFont(MENU_FONT1)
470             .setUserValue(String("Files"))
471             .setAction(Widget::Deactivated, Hu_MenuActionSetActivePage)
472             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
473 
474     y += FIXED_LINE_HEIGHT;
475 #endif
476 
477 #if !__JDOOM64__
478     page->addWidget(new ButtonWidget)
479 # if defined(__JDOOM__)
480             .setPatch(pReadThis)
481 # else
482             .setText("Info")
483 # endif
484             .setFixedY(y)
485 # if __JDOOM__
486             .setFlags(Widget::Id0)
487             .setShortcut('r')
488 # else
489             .setShortcut('i')
490 # endif
491             .setFont(MENU_FONT1)
492             .setAction(Widget::Deactivated, Hu_MenuSelectHelp)
493             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
494 
495     y += FIXED_LINE_HEIGHT;
496 #endif
497 
498     page->addWidget(new ButtonWidget)
499 #if defined(__JDOOM__) && !defined(__JDOOM64__)
500             .setPatch(pQuitGame)
501 #else
502             .setText("Quit Game")
503 #endif
504 #if __JDOOM__
505             .setFlags(Widget::Id1)
506 #endif
507             .setFixedY(y)
508             .setShortcut('q')
509             .setFont(MENU_FONT1)
510             .setAction(Widget::Deactivated, Hu_MenuSelectQuitGame)
511             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
512 }
513 
514 #if 0
515 void Hu_MenuInitGameTypePage()
516 {
517 #if __JDOOM__ || __JDOOM64__
518     Vector2i origin(97, 65);
519 #else
520     Vector2i origin(104, 65);
521 #endif
522 
523     Page *page = Hu_MenuAddPage(new Page("GameType", origin, 0, Hu_MenuDrawGameTypePage));
524     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTB));
525     page->setPreviousPage(Hu_MenuPagePtr("Main"));
526 
527     int y = 0;
528 
529     String labelText = GET_TXT(TXT_SINGLEPLAYER);
530     int shortcut     = labelText.first().isLetterOrNumber()? labelText.first().toLatin1() : 0;
531     page->addWidget(new ButtonWidget(labelText))
532             .setFixedY(y)
533             .setFont(MENU_FONT1)
534             .setShortcut(shortcut)
535             .setAction(Widget::Deactivated, Hu_MenuSelectSingleplayer)
536             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
537 
538     y += FIXED_LINE_HEIGHT;
539 
540     labelText = GET_TXT(TXT_MULTIPLAYER);
541     shortcut  = labelText.first().isLetterOrNumber()? labelText.first().toLatin1() : 0;
542     page->addWidget(new ButtonWidget(labelText))
543             .setFixedY(y)
544             .setFont(MENU_FONT1)
545             .setShortcut(shortcut)
546             .setAction(Widget::Deactivated, Hu_MenuSelectMultiplayer)
547             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
548 }
549 #endif
550 
Hu_MenuInitSkillPage()551 void Hu_MenuInitSkillPage()
552 {
553 #if __JHEXEN__
554     Vector2i const origin(120, 44);
555 #elif __JHERETIC__
556     Vector2i const origin(38, 30);
557 #else
558     Vector2i const origin(48, 63);
559 #endif
560     Widget::Flags skillButtonFlags[NUM_SKILL_MODES] = {
561         Widget::Id0,
562         Widget::Id1,
563         Widget::Id2 | Widget::DefaultFocus,
564         Widget::Id3,
565 #  if !__JDOOM64__
566         Widget::Id4
567 #  endif
568     };
569 #if !__JHEXEN__
570     int skillButtonTexts[NUM_SKILL_MODES] = {
571         TXT_SKILL1,
572         TXT_SKILL2,
573         TXT_SKILL3,
574         TXT_SKILL4,
575 #  if !__JDOOM64__
576         TXT_SKILL5
577 #  endif
578     };
579 #endif
580 
581     Page *page = Hu_MenuAddPage(new Page("Skill", origin, Page::FixedLayout | Page::NoScroll,
582                                          Hu_MenuDrawSkillPage, Hu_MenuSkipPreviousPageIfSkippingEpisodeSelection));
583     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTB));
584     page->setPreviousPage(Hu_MenuPagePtr("Episode"));
585 
586     int y = 0;
587 
588     for(uint i = 0; i < NUM_SKILL_MODES; ++i, y += FIXED_LINE_HEIGHT)
589     {
590 #if !__JHEXEN__
591         String const labelText = GET_TXT(skillButtonTexts[i]);
592         int const shortcut     = labelText.first().isLetterOrNumber()? labelText.first().toLatin1() : 0;
593 #endif
594 
595         page->addWidget(new ButtonWidget)
596 #if !__JHEXEN__
597                 .setText(labelText)
598 # if __JDOOM__ || __JDOOM64__
599                 .setPatch(pSkillModeNames[i])
600 # endif
601                 .setShortcut(shortcut)
602 #endif
603                 .setFlags(skillButtonFlags[i])
604                 .setFixedY(y)
605                 .setFont(MENU_FONT1)
606                 .setUserValue2(int(SM_BABY + i))
607                 .setAction(Widget::Deactivated, Hu_MenuActionInitNewGame)
608                 .setAction(Widget::FocusGained, Hu_MenuFocusSkillMode);
609     }
610 
611 #if __JDOOM__
612     if(gameMode != doom2_hacx && gameMode != doom_chex)
613     {
614         page->findWidget(Widget::Id4).as<ButtonWidget>().setNoAltText();
615     }
616 #endif
617 }
618 
619 #if 0
620 void Hu_MenuInitMultiplayerPage()
621 {
622 #if __JHERETIC__ || __JHEXEN__
623     Vector2i const origin(97, 65);
624 #else
625     Vector2i const origin(97, 65);
626 #endif
627 
628     Page *page = Hu_MenuAddPage(new Page("Multiplayer", origin, 0, Hu_MenuDrawMultiplayerPage));
629     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTB));
630     page->setPreviousPage(Hu_MenuPagePtr("GameType"));
631 
632     page->addWidget(new ButtonWidget("Join Game"))
633             .setFlags(Widget::Id0)
634             .setShortcut('j')
635             .setFont(MENU_FONT1)
636             .setAction(Widget::Deactivated, Hu_MenuSelectJoinGame)
637             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
638 
639     page->addWidget(new ButtonWidget("Player Setup"))
640             .setShortcut('p')
641             .setFont(MENU_FONT1)
642             .setUserValue(String("PlayerSetup"))
643             .setAction(Widget::Deactivated, Hu_MenuActionSetActivePage)
644             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
645 }
646 #endif
647 
Hu_MenuInitPlayerSetupPage()648 void Hu_MenuInitPlayerSetupPage()
649 {
650 #if __JHERETIC__ || __JHEXEN__
651     Vector2i const origin(70, 34);
652 #else
653     Vector2i const origin(70, 54);
654 #endif
655 
656     Page *page = Hu_MenuAddPage(new Page("PlayerSetup", origin, Page::NoScroll, Hu_MenuDrawPlayerSetupPage));
657     page->setLeftColumnWidth(.5f);
658     page->setOnActiveCallback(Hu_MenuActivatePlayerSetup);
659     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTA));
660     page->setPredefinedFont(MENU_FONT2, FID(GF_FONTB));
661     page->setPreviousPage(Hu_MenuPagePtr("Options"));
662 
663     page->addWidget(new MobjPreviewWidget)
664             .setFixedOrigin(Vector2i(SCREENWIDTH / 2 - 40, 60))
665             .setFlags(Widget::Id0 | Widget::PositionFixed);
666 
667     page->addWidget(new CVarLineEditWidget("net-name"))
668             .setMaxLength(24)
669             .setFlags(Widget::Id1 | Widget::LayoutOffset)
670             .setFixedY(75);
671 
672 #if __JHEXEN__
673     page->addWidget(new LabelWidget("Class"))
674             .setLeft()
675             .setFlags(Widget::LayoutOffset)
676             .setFixedY(5);
677 
678     page->addWidget(new InlineListWidget)
679             .addItems(ListWidget::Items() << new ListWidgetItem(GET_TXT(TXT_PLAYERCLASS1), PCLASS_FIGHTER)
680                                           << new ListWidgetItem(GET_TXT(TXT_PLAYERCLASS2), PCLASS_CLERIC)
681                                           << new ListWidgetItem(GET_TXT(TXT_PLAYERCLASS3), PCLASS_MAGE))
682             .setFlags(Widget::Id2)
683             .setShortcut('c')
684             .setRight()
685             .setColor(MENU_COLOR3)
686             .setAction(Widget::Modified,    Hu_MenuSelectPlayerSetupPlayerClass)
687             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
688 #endif
689 
690     auto &label = page->addWidget(new LabelWidget("Color"));
691     label.setLeft();
692 #ifdef __JHERETIC__
693     label.setFlags(Widget::LayoutOffset);
694     label.setFixedY(5);
695 #else
696     DENG2_UNUSED(label);
697 #endif
698 
699     // Setup the player color selection list.
700     /// @todo Read these names from Text definitions.
701     int colorIdx = 0;
702     ListWidget::Items items;
703 #if __JHEXEN__
704     items << new ListWidgetItem("Red",    colorIdx++);
705     items << new ListWidgetItem("Blue",   colorIdx++);
706     items << new ListWidgetItem("Yellow", colorIdx++);
707     items << new ListWidgetItem("Green",  colorIdx++);
708     if(gameMode != hexen_v10) // Hexen v1.0 has only four player colors.
709     {
710         items << new ListWidgetItem("Jade",   colorIdx++);
711         items << new ListWidgetItem("White",  colorIdx++);
712         items << new ListWidgetItem("Hazel",  colorIdx++);
713         items << new ListWidgetItem("Purple", colorIdx++);
714     }
715 #elif __JHERETIC__
716     items << new ListWidgetItem("Green",  colorIdx++);
717     items << new ListWidgetItem("Orange", colorIdx++);
718     items << new ListWidgetItem("Red",    colorIdx++);
719     items << new ListWidgetItem("Blue",   colorIdx++);
720 #else
721     items << new ListWidgetItem("Green",  colorIdx++);
722     items << new ListWidgetItem("Indigo", colorIdx++);
723     items << new ListWidgetItem("Brown",  colorIdx++);
724     items << new ListWidgetItem("Red",    colorIdx++);
725 #endif
726     items << new ListWidgetItem("Automatic", colorIdx++);
727 
728     page->addWidget(new InlineListWidget)
729             .addItems(items)
730             .setFlags(Widget::Id3)
731             .setColor(MENU_COLOR3)
732             .setRight()
733             .setAction(Widget::Modified,    Hu_MenuSelectPlayerColor)
734             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
735 
736     page->addWidget(new ButtonWidget("Save Changes"))
737             .setShortcut('s')
738             .setAction(Widget::Deactivated, Hu_MenuSelectAcceptPlayerSetup)
739             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
740 }
741 
Hu_MenuInitSaveOptionsPage()742 void Hu_MenuInitSaveOptionsPage()
743 {
744     Page *page = Hu_MenuAddPage(new Page("SaveOptions", Vector2i(60, 50)));
745     page->setTitle("Savegame Options");
746     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTA));
747     page->setPreviousPage(Hu_MenuPagePtr("Options"));
748 
749     page->addWidget(new LabelWidget("Confirm quick load/save"))
750             .setLeft();
751     page->addWidget(new CVarToggleWidget("game-save-confirm"))
752             .setRight()
753             .setShortcut('q');
754 
755     page->addWidget(new LabelWidget("Confirm reborn load"))
756             .setLeft();
757     page->addWidget(new CVarToggleWidget("game-save-confirm-loadonreborn"))
758             .setRight()
759             .setShortcut('r');
760 
761     page->addWidget(new LabelWidget("Reborn preferences"))
762             .setGroup(1)
763             .setColor(MENU_COLOR2);
764 
765     page->addWidget(new LabelWidget("Load last save"))
766             .setLeft()
767             .setGroup(1);
768     page->addWidget(new CVarToggleWidget("game-save-last-loadonreborn"))
769             .setRight()
770             .setGroup(1)
771             .setShortcut('a');
772 }
773 
774 #if __JHERETIC__ || __JHEXEN__
Hu_MenuInitFilesPage()775 void Hu_MenuInitFilesPage()
776 {
777     Page *page = Hu_MenuAddPage(new Page("Files", Vector2i(110, 60), Page::FixedLayout | Page::NoScroll));
778     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTB));
779     page->setPreviousPage(Hu_MenuPagePtr("Main"));
780 
781     int y = 0;
782 
783     page->addWidget(new ButtonWidget("Load Game"))
784             .setFixedY(y)
785             .setShortcut('l')
786             .setFont(MENU_FONT1)
787             .setAction(Widget::Deactivated, Hu_MenuSelectLoadGame)
788             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
789 
790     y += FIXED_LINE_HEIGHT;
791 
792     page->addWidget(new ButtonWidget("Save Game"))
793             .setFixedY(y)
794             .setShortcut('s')
795             .setFont(MENU_FONT1)
796             .setAction(Widget::Deactivated, Hu_MenuSelectSaveGame)
797             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
798 }
799 #endif
800 
deleteGameSave(String slotId)801 static void deleteGameSave(String slotId)
802 {
803     DD_Executef(true, "deletegamesave %s", slotId.toLatin1().constData());
804 }
805 
Hu_MenuLoadSlotCommandResponder(Widget & wi,menucommand_e cmd)806 int Hu_MenuLoadSlotCommandResponder(Widget &wi, menucommand_e cmd)
807 {
808     LineEditWidget &edit = wi.as<LineEditWidget>();
809     if(cmd == MCMD_DELETE && !wi.isDisabled() && wi.isFocused() && !wi.isActive())
810     {
811         deleteGameSave(edit.userValue().toString());
812         return true;
813     }
814     if(cmd == MCMD_SELECT && !wi.isDisabled() && wi.isFocused())
815     {
816         S_LocalSound(SFX_MENU_ACCEPT, NULL);
817         if(!wi.isActive())
818         {
819             wi.setFlags(Widget::Active);
820             wi.execAction(Widget::Activated);
821         }
822 
823         wi.setFlags(Widget::Active, UnsetFlags);
824         wi.execAction(Widget::Deactivated);
825         return true;
826     }
827     return false; // Not eaten.
828 }
829 
Hu_MenuSaveSlotCommandResponder(Widget & wi,menucommand_e cmd)830 int Hu_MenuSaveSlotCommandResponder(Widget &wi, menucommand_e cmd)
831 {
832     LineEditWidget &edit = wi.as<LineEditWidget>();
833     if(cmd == MCMD_DELETE && !wi.isDisabled() && wi.isFocused() && !wi.isActive())
834     {
835         deleteGameSave(edit.userValue().toString());
836         return true;
837     }
838     return edit.handleCommand(cmd);
839 }
840 
Hu_MenuInitLoadGameAndSaveGamePages()841 void Hu_MenuInitLoadGameAndSaveGamePages()
842 {
843 #if __JDOOM__ || __JDOOM64__
844     Vector2i const origin(50, 54);
845 #else
846     Vector2i const origin(40, 30);
847 #endif
848     Widget::Flags const saveSlotObjectIds[NUMSAVESLOTS] = {
849         Widget::Id0, Widget::Id1, Widget::Id2, Widget::Id3, Widget::Id4, Widget::Id5,
850 #if !__JHEXEN__
851         Widget::Id6, Widget::Id7
852 #endif
853     };
854 
855     Page *loadPage = Hu_MenuAddPage(new Page("LoadGame", origin, Page::FixedLayout | Page::NoScroll, Hu_MenuDrawLoadGamePage));
856     loadPage->setPredefinedFont(MENU_FONT1, FID(GF_FONTA));
857     loadPage->setPreviousPage(Hu_MenuPagePtr("Main"));
858 
859     int y = 0;
860     int i = 0;
861     for(; i < NUMSAVESLOTS; ++i, y += FIXED_LINE_HEIGHT)
862     {
863         loadPage->addWidget(new LineEditWidget)
864                     .setMaxLength(24)
865                     .setEmptyText(GET_TXT(TXT_EMPTYSTRING))
866                     .setFixedY(y)
867                     .setFlags(saveSlotObjectIds[i] | Widget::Disabled)
868                     .setShortcut('0' + i)
869                     .setCommandResponder(Hu_MenuLoadSlotCommandResponder)
870                     .setUserValue(String::number(i))
871                     .setUserValue2(int(saveSlotObjectIds[i]))
872                     .setAction(Widget::Deactivated, Hu_MenuSelectLoadSlot)
873                     .setAction(Widget::FocusLost,   Hu_MenuDefaultFocusAction);
874     }
875 
876     Page *savePage = Hu_MenuAddPage(new Page("SaveGame", origin, Page::FixedLayout | Page::NoScroll, Hu_MenuDrawSaveGamePage));
877     savePage->setPredefinedFont(MENU_FONT1, FID(GF_FONTA));
878     savePage->setPreviousPage(Hu_MenuPagePtr("Main"));
879 
880     y = 0;
881     i = 0;
882     for(; i < NUMSAVESLOTS; ++i, y += FIXED_LINE_HEIGHT)
883     {
884         savePage->addWidget(new LineEditWidget)
885                     .setMaxLength(24)
886                     .setEmptyText(GET_TXT(TXT_EMPTYSTRING))
887                     .setFixedY(y)
888                     .setFlags(saveSlotObjectIds[i])
889                     .setShortcut('0' + i)
890                     .setCommandResponder(Hu_MenuSaveSlotCommandResponder)
891                     .setUserValue(String::number(i))
892                     .setUserValue2(int(saveSlotObjectIds[i]))
893                     .setAction(Widget::Deactivated, Hu_MenuSelectSaveSlot)
894                     .setAction(Widget::Activated,   Hu_MenuSaveSlotEdit)
895                     .setAction(Widget::FocusLost,   Hu_MenuDefaultFocusAction);
896     }
897 }
898 
Hu_MenuInitOptionsPage()899 void Hu_MenuInitOptionsPage()
900 {
901 #if __JHERETIC__ || __JHEXEN__
902     Vector2i const origin(110, 45);
903 #else
904     Vector2i const origin(110, 63);
905 #endif
906 
907     Page *page = Hu_MenuAddPage(new Page("Options", origin, Page::NoScroll, Hu_MenuDrawOptionsPage));
908     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTA));
909     page->setPreviousPage(Hu_MenuPagePtr("Main"));
910 
911     page->addWidget(new ButtonWidget("End Game"))
912             .setShortcut('e')
913             .setFont(MENU_FONT1)
914             .setGroup(1)
915             .setAction(Widget::Deactivated, Hu_MenuSelectEndGame)
916             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
917 
918     page->addWidget(new ButtonWidget("Player Setup"))
919             .setShortcut('p')
920             .setGroup(1)
921             .setFont(MENU_FONT1)
922             .setUserValue(String("PlayerSetup"))
923             .setAction(Widget::Deactivated, Hu_MenuActionSetActivePage)
924             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
925 
926     page->addWidget(new ButtonWidget("Show Taskbar"))
927             .setShortcut('t')
928             .setFont(MENU_FONT1)
929             .setGroup(1)
930             .setAction(Widget::Deactivated, Hu_MenuSelectControlPanelLink)
931             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
932 
933     page->addWidget(new ButtonWidget("Sound"))
934             .setShortcut('s')
935             .setFont(MENU_FONT1)
936             .setUserValue(String("SoundOptions"))
937             .setAction(Widget::Deactivated, Hu_MenuActionSetActivePage)
938             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
939 
940     page->addWidget(new ButtonWidget("Controls"))
941             .setShortcut('c')
942             .setFont(MENU_FONT1)
943             .setUserValue(String("ControlOptions"))
944             .setAction(Widget::Deactivated, Hu_MenuActionSetActivePage)
945             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
946 
947     page->addWidget(new ButtonWidget("Gameplay"))
948             .setShortcut('g')
949             .setFont(MENU_FONT1)
950             .setUserValue(String("GameplayOptions"))
951             .setAction(Widget::Deactivated, Hu_MenuActionSetActivePage)
952             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
953 
954     page->addWidget(new ButtonWidget("HUD"))
955             .setShortcut('h')
956             .setFont(MENU_FONT1)
957             .setUserValue(String("HUDOptions"))
958             .setAction(Widget::Deactivated, Hu_MenuActionSetActivePage)
959             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
960 
961     page->addWidget(new ButtonWidget("Automap"))
962             .setShortcut('a')
963             .setFont(MENU_FONT1)
964             .setUserValue(String("AutomapOptions"))
965             .setAction(Widget::Deactivated, Hu_MenuActionSetActivePage)
966             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
967 
968     page->addWidget(new ButtonWidget("Weapons"))
969             .setShortcut('w')
970             .setFont(MENU_FONT1)
971             .setUserValue(String("WeaponOptions"))
972             .setAction(Widget::Deactivated, Hu_MenuActionSetActivePage)
973             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
974 
975 #if __JHERETIC__ || __JHEXEN__
976     page->addWidget(new ButtonWidget("Inventory"))
977             .setShortcut('i')
978             .setFont(MENU_FONT1)
979             .setUserValue(String("InventoryOptions"))
980             .setAction(Widget::Deactivated, Hu_MenuActionSetActivePage)
981             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
982 #endif
983 
984     page->addWidget(new ButtonWidget("Savegame"))
985             .setShortcut('s')
986             .setFont(MENU_FONT1)
987             .setUserValue(String("SaveOptions"))
988             .setAction(Widget::Deactivated, Hu_MenuActionSetActivePage)
989             .setAction(Widget::FocusGained,     Hu_MenuDefaultFocusAction);
990 }
991 
Hu_MenuInitGameplayOptionsPage()992 void Hu_MenuInitGameplayOptionsPage()
993 {
994 #if __JHEXEN__
995     Vector2i const origin(88, 25);
996 #elif __JHERETIC__
997     Vector2i const origin(30, 40);
998 #else
999     Vector2i const origin(30, 40);
1000 #endif
1001 
1002     Page *page = Hu_MenuAddPage(new Page("GameplayOptions", origin));
1003     page->setLeftColumnWidth(.75f);
1004     page->setTitle("Gameplay Options");
1005     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTA));
1006     page->setPreviousPage(Hu_MenuPagePtr("Options"));
1007 
1008     page->addWidget(new LabelWidget("Always Run")).setLeft();
1009     page->addWidget(new CVarToggleWidget("ctl-run")).setRight()
1010             .setShortcut('r');
1011 
1012     page->addWidget(new LabelWidget("Use LookSpring")).setLeft();
1013     page->addWidget(new CVarToggleWidget("ctl-look-spring")).setRight()
1014             .setShortcut('l');
1015 
1016     page->addWidget(new LabelWidget("Disable AutoAim")).setLeft();
1017     page->addWidget(new CVarToggleWidget("ctl-aim-noauto")).setRight()
1018             .setShortcut('a');
1019 
1020 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
1021     page->addWidget(new LabelWidget("Allow Jumping")).setLeft();
1022     page->addWidget(new CVarToggleWidget("player-jump")).setRight()
1023             .setShortcut('j');
1024 #endif
1025 
1026 #if __JDOOM__
1027     page->addWidget(new LabelWidget("Fast Monsters")).setLeft();
1028     page->addWidget(new CVarToggleWidget("game-monsters-fast")).setRight()
1029             .setShortcut('f');
1030 #endif
1031 
1032 #if __JDOOM64__
1033     page->addWidget(new LabelWidget("Weapon Recoil")).setLeft();
1034     page->addWidget(new CVarToggleWidget("player-weapon-recoil")).setRight();
1035 #endif
1036 
1037 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
1038     page->addWidget(new LabelWidget("Compatibility"))
1039             .setLeft()
1040             .setGroup(1)
1041             .setColor(MENU_COLOR2);
1042 
1043 # if __JDOOM__ || __JDOOM64__
1044     page->addWidget(new LabelWidget("Any Boss Trigger 666")).setLeft()
1045             .setGroup(1);
1046     page->addWidget(new CVarToggleWidget("game-anybossdeath666")).setRight()
1047             .setGroup(1)
1048             .setShortcut('b');
1049 
1050 #  if !__JDOOM64__
1051     page->addWidget(new LabelWidget("Av Resurrects Ghosts"))
1052             .setLeft()
1053             .setGroup(1);
1054     page->addWidget(new CVarToggleWidget("game-raiseghosts"))
1055             .setRight()
1056             .setGroup(1)
1057             .setShortcut('g');
1058 # if __JDOOM__
1059     page->addWidget(new LabelWidget("VileChase uses Av radius"))
1060             .setLeft()
1061             .setGroup(1);
1062     page->addWidget(new CVarToggleWidget("game-vilechase-usevileradius"))
1063             .setRight()
1064             .setGroup(1)
1065             .setShortcut('g');
1066 # endif
1067 #  endif // !__JDOOM64__
1068 
1069     page->addWidget(new LabelWidget("PE Limited To 21 Lost Souls"))
1070             .setLeft()
1071             .setGroup(1);
1072     page->addWidget(new CVarToggleWidget("game-maxskulls"))
1073             .setRight()
1074             .setGroup(1)
1075             .setShortcut('p');
1076 
1077     page->addWidget(new LabelWidget("LS Can Get Stuck Inside Walls"))
1078             .setLeft()
1079             .setGroup(1);
1080 
1081     page->addWidget(new CVarToggleWidget("game-skullsinwalls"))
1082             .setRight()
1083             .setGroup(1);
1084 # endif // __JDOOM__ || __JDOOM64__
1085 
1086     page->addWidget(new LabelWidget("Monsters Fly Over Obstacles"))
1087             .setLeft()
1088             .setGroup(1);
1089     page->addWidget(new CVarToggleWidget("game-monsters-floatoverblocking"))
1090             .setRight()
1091             .setGroup(1);
1092 
1093     page->addWidget(new LabelWidget("Monsters Can Get Stuck\n   In Doors"))
1094             .setLeft()
1095             .setGroup(1);
1096     page->addWidget(new CVarToggleWidget("game-monsters-stuckindoors"))
1097             .setRight()
1098             .setGroup(1)
1099             .setShortcut('d');
1100 
1101     page->addWidget(new LabelWidget("Some Objects Never Hang\n   Over Ledges"))
1102             .setLeft()
1103             .setGroup(1);
1104     page->addWidget(new CVarToggleWidget("game-objects-neverhangoverledges"))
1105             .setRight()
1106             .setGroup(1)
1107             .setShortcut('h');
1108 
1109     page->addWidget(new LabelWidget("Objects Fall Under Own Weight"))
1110             .setLeft()
1111             .setGroup(1);
1112     page->addWidget(new CVarToggleWidget("game-objects-falloff"))
1113             .setRight()
1114             .setGroup(1)
1115             .setShortcut('f');
1116 
1117 #if __JDOOM__ || __JDOOM64__
1118     page->addWidget(new LabelWidget("All Crushed Objects\n   Become A Pile Of Gibs"))
1119             .setLeft()
1120             .setGroup(1);
1121     page->addWidget(new CVarToggleWidget("game-objects-gibcrushednonbleeders"))
1122             .setRight()
1123             .setGroup(1)
1124             .setShortcut('g');
1125 #endif
1126 
1127     page->addWidget(new LabelWidget("Corpses Slide Down Stairs"))
1128             .setLeft()
1129             .setGroup(1);
1130 
1131     page->addWidget(new CVarToggleWidget("game-corpse-sliding"))
1132             .setRight()
1133             .setGroup(1)
1134             .setShortcut('s');
1135 
1136     page->addWidget(new LabelWidget("Use Doom's Clipping\n   Code Exactly"))
1137             .setLeft()
1138             .setGroup(1);
1139 
1140     page->addWidget(new CVarToggleWidget("game-objects-clipping"))
1141             .setRight()
1142             .setGroup(1)
1143             .setShortcut('c');
1144 
1145     page->addWidget(new LabelWidget("  ^If Not NorthOnly WallRunning"))
1146             .setLeft()
1147             .setGroup(1);
1148     page->addWidget(new CVarToggleWidget("game-player-wallrun-northonly"))
1149             .setRight()
1150             .setGroup(1)
1151             .setShortcut('w');
1152 
1153     page->addWidget(new LabelWidget("Pushable Speed Limit"))
1154             .setLeft()
1155             .setGroup(1);
1156     page->addWidget(new CVarToggleWidget("game-objects-pushable-limit"))
1157             .setRight()
1158             .setGroup(1)
1159             .setShortcut('p');
1160 
1161 # if __JDOOM__ || __JDOOM64__
1162 
1163     page->addWidget(new LabelWidget("Zombie Players Can\n   Exit Maps")).setLeft()
1164             .setGroup(1);
1165     page->addWidget(new CVarToggleWidget("game-zombiescanexit")).setRight()
1166             .setGroup(1)
1167             .setShortcut('e');
1168 
1169     page->addWidget(new LabelWidget("Fix Ouch Face")).setLeft()
1170             .setGroup(1);
1171     page->addWidget(new CVarToggleWidget("hud-face-ouchfix")).setRight()
1172             .setGroup(1);
1173 
1174     page->addWidget(new LabelWidget("Fix Weapon Slot Display")).setLeft()
1175             .setGroup(1);
1176     page->addWidget(new CVarToggleWidget("hud-status-weaponslots-ownedfix")).setRight()
1177             .setGroup(1);
1178 
1179 # endif // __JDOOM__ || __JDOOM64__
1180 #endif // __JDOOM__ || __JHERETIC__ || __JDOOM64__
1181 
1182 #if __JHERETIC__
1183     page->addWidget(new LabelWidget("Powered Staff Damages Ghosts"))
1184             .setLeft()
1185             .setGroup(1);
1186     page->addWidget(new CVarToggleWidget("player-weapon-staff-powerghostdamage"))
1187             .setRight()
1188             .setGroup(1)
1189             .setShortcut('g');
1190 #endif
1191 
1192     page->addWidget(new LabelWidget("Vanilla Switch Sound\n   Positioning"))
1193             .setLeft()
1194             .setGroup(1);
1195     page->addWidget(new CVarToggleWidget("sound-switch-origin"))
1196             .setRight()
1197             .setGroup(1)
1198             .setShortcut('v');
1199 }
1200 
Hu_MenuInitHUDOptionsPage()1201 void Hu_MenuInitHUDOptionsPage()
1202 {
1203 #if __JDOOM__ || __JDOOM64__
1204     Vector2i const origin(97, 40);
1205 #else
1206     Vector2i const origin(97, 28);
1207 #endif
1208 
1209     Page *page = Hu_MenuAddPage(new Page("HudOptions", origin));
1210     page->setTitle("HUD Options");
1211     page->setLeftColumnWidth(.45f);
1212     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTA));
1213     page->setPreviousPage(Hu_MenuPagePtr("Options"));
1214 
1215     page->addWidget(new LabelWidget("View Size")).setLeft();
1216 
1217     page->addWidget(new CVarSliderWidget("view-size"))
1218 #if __JDOOM64__
1219             .setRange(3, 11, 1)
1220 #else
1221             .setRange(3, 13, 1)
1222 #endif
1223             .setFloatMode(false)
1224             .setRight();
1225 
1226     page->addWidget(new LabelWidget("Messages"))
1227             .setGroup(2)
1228             .setColor(MENU_COLOR2);
1229 
1230     page->addWidget(new LabelWidget("Shown"))
1231             .setLeft()
1232             .setGroup(2);
1233     page->addWidget(new CVarToggleWidget("msg-show"))
1234             .setRight()
1235             .setGroup(2)
1236             .setShortcut('m');
1237 
1238     page->addWidget(new LabelWidget("Uptime"))
1239             .setLeft()
1240             .setGroup(2);
1241     page->addWidget(new CVarTextualSliderWidget("msg-uptime", 0, 60, 1))
1242             .setEmptyText("Disabled")
1243             .setOnethSuffix(" second")
1244             .setNthSuffix(" seconds")
1245             .setRight()
1246             .setGroup(2);
1247 
1248     page->addWidget(new LabelWidget("Size"))
1249             .setLeft()
1250             .setGroup(2);
1251     page->addWidget(new CVarSliderWidget("msg-scale"))
1252             .setRight()
1253             .setGroup(2);
1254 
1255     page->addWidget(new LabelWidget("Color"))
1256             .setLeft()
1257             .setGroup(2);
1258     page->addWidget(new CVarColorEditWidget("msg-color-r", "msg-color-g", "msg-color-b"))
1259             .setRight()
1260             .setGroup(2)
1261             .setAction(Widget::Deactivated, CVarColorEditWidget_UpdateCVar)
1262             .setAction(Widget::Activated,   Hu_MenuActivateColorWidget);
1263 
1264     page->addWidget(new LabelWidget("Crosshair"))
1265             .setGroup(3)
1266             .setColor(MENU_COLOR2);
1267 
1268     page->addWidget(new LabelWidget("Symbol"))
1269             .setGroup(3)
1270             .setLeft()
1271             .setShortcut('c');
1272     page->addWidget(new CVarInlineListWidget("view-cross-type"))
1273             .addItems(ListWidget::Items() << new ListWidgetItem("None",        0)
1274                                           << new ListWidgetItem("Cross",       1)
1275                                           << new ListWidgetItem("Twin Angles", 2)
1276                                           << new ListWidgetItem("Square",      3)
1277                                           << new ListWidgetItem("Open Square", 4)
1278                                           << new ListWidgetItem("Angle",       5))
1279             .setGroup(3)
1280             .setRight();
1281 
1282     page->addWidget(new LabelWidget("Size"))
1283             .setLeft()
1284             .setGroup(3);
1285     page->addWidget(new CVarSliderWidget("view-cross-size"))
1286             .setRight()
1287             .setGroup(3);
1288 
1289     page->addWidget(new LabelWidget("Thickness"))
1290             .setLeft()
1291             .setGroup(3);
1292     page->addWidget(new CVarSliderWidget("view-cross-width", .5f, 5, .5f))
1293             .setRight()
1294             .setGroup(3);
1295 
1296     page->addWidget(new LabelWidget("Angle"))
1297             .setLeft()
1298             .setGroup(3);
1299     page->addWidget(new CVarSliderWidget("view-cross-angle", 0.0f, 1.0f, 0.0625f))
1300             .setRight()
1301             .setGroup(3);
1302 
1303     page->addWidget(new LabelWidget("Opacity"))
1304             .setLeft()
1305             .setGroup(3);
1306     page->addWidget(new CVarSliderWidget("view-cross-a"))
1307             .setRight()
1308             .setGroup(3);
1309 
1310     page->addWidget(new LabelWidget("Color"))
1311             .setLeft()
1312             .setGroup(3);
1313     page->addWidget(new CVarColorEditWidget("view-cross-r", "view-cross-g", "view-cross-b"))
1314             .setRight()
1315             .setGroup(3)
1316             .setAction(Widget::Deactivated, CVarColorEditWidget_UpdateCVar)
1317             .setAction(Widget::Activated,   Hu_MenuActivateColorWidget);
1318 
1319     page->addWidget(new LabelWidget("Vitality Color"))
1320             .setLeft()
1321             .setGroup(3);
1322     page->addWidget(new CVarToggleWidget("view-cross-vitality"))
1323             .setRight()
1324             .setGroup(3);
1325 
1326     page->addWidget(new LabelWidget("   When Dead"))
1327             .setLeft()
1328             .setGroup(3);
1329     page->addWidget(new CVarColorEditWidget("view-cross-dead-r", "view-cross-dead-g", "view-cross-dead-b"))
1330             .setRight()
1331             .setGroup(3)
1332             .setAction(Widget::Deactivated,     CVarColorEditWidget_UpdateCVar)
1333             .setAction(Widget::Activated,       Hu_MenuActivateColorWidget);
1334 
1335     page->addWidget(new LabelWidget("   Full Health"))
1336             .setLeft()
1337             .setGroup(3);
1338 
1339     page->addWidget(new CVarColorEditWidget("view-cross-live-r", "view-cross-live-g", "view-cross-live-b"))
1340             .setRight()
1341             .setGroup(3)
1342             .setAction(Widget::Deactivated,     CVarColorEditWidget_UpdateCVar)
1343             .setAction(Widget::Activated,       Hu_MenuActivateColorWidget);
1344 
1345 #if __JDOOM__ || __JHERETIC__ || __JHEXEN__
1346     page->addWidget(new LabelWidget("Statusbar"))
1347             .setGroup(4)
1348             .setColor(MENU_COLOR2);
1349 
1350     page->addWidget(new LabelWidget("Size"))
1351             .setLeft()
1352             .setGroup(4);
1353     page->addWidget(new CVarSliderWidget("hud-status-size"))
1354             .setRight()
1355             .setGroup(4);
1356 
1357     page->addWidget(new LabelWidget("Opacity"))
1358             .setLeft()
1359             .setGroup(4);
1360     page->addWidget(new CVarSliderWidget("hud-status-alpha"))
1361             .setRight()
1362             .setGroup(4);
1363 
1364 #if __JDOOM__
1365     page->addWidget(new LabelWidget("Single Key Display")).setLeft().setGroup(4);
1366     page->addWidget(new CVarToggleWidget("hud-keys-combine")).setRight().setGroup(4);
1367 #endif
1368 
1369     page->addWidget(new LabelWidget("AutoHide Status"))
1370             .setLeft()
1371             .setGroup(4);
1372     page->addWidget(new CVarTextualSliderWidget("hud-timer", 0, 60, 1))
1373             .setEmptyText("Disabled")
1374             .setOnethSuffix(" second")
1375             .setNthSuffix(" seconds")
1376             .setRight()
1377             .setGroup(4);
1378 
1379     page->addWidget(new LabelWidget("Status UnHide Events"))
1380             .setGroup(1)
1381             .setColor(MENU_COLOR2);
1382 
1383     page->addWidget(new LabelWidget("Receive Damage"))
1384             .setLeft()
1385             .setGroup(1);
1386     page->addWidget(new CVarToggleWidget("hud-unhide-damage"))
1387             .setRight()
1388             .setGroup(1);
1389 
1390     page->addWidget(new LabelWidget("Pickup Health"))
1391             .setLeft()
1392             .setGroup(1);
1393     page->addWidget(new CVarToggleWidget("hud-unhide-pickup-health"))
1394             .setRight()
1395             .setGroup(1);
1396 
1397     page->addWidget(new LabelWidget("Pickup Armor"))
1398             .setLeft()
1399             .setGroup(1);
1400     page->addWidget(new CVarToggleWidget("hud-unhide-pickup-armor"))
1401             .setRight()
1402             .setGroup(1);
1403 
1404     page->addWidget(new LabelWidget("Pickup Powerup"))
1405             .setLeft()
1406             .setGroup(1);
1407     page->addWidget(new CVarToggleWidget("hud-unhide-pickup-powerup"))
1408             .setRight()
1409             .setGroup(1);
1410 
1411     page->addWidget(new LabelWidget("Pickup Weapon"))
1412             .setLeft()
1413             .setGroup(1);
1414     page->addWidget(new CVarToggleWidget("hud-unhide-pickup-weapon"))
1415             .setRight()
1416             .setGroup(1);
1417 
1418     page->addWidget(new LabelWidget)
1419 #if __JHEXEN__
1420             .setText("Pickup Mana")
1421 #else
1422             .setText("Pickup Ammo")
1423 #endif
1424             .setGroup(1)
1425             .setLeft();
1426     page->addWidget(new CVarToggleWidget("hud-unhide-pickup-ammo"))
1427             .setRight()
1428             .setGroup(1);
1429 
1430     page->addWidget(new LabelWidget("Pickup Key"))
1431             .setLeft()
1432             .setGroup(1);
1433     page->addWidget(new CVarToggleWidget("hud-unhide-pickup-key"))
1434             .setRight()
1435             .setGroup(1);
1436 
1437 #if __JHERETIC__ || __JHEXEN__
1438     page->addWidget(new LabelWidget("Pickup Item"))
1439             .setLeft()
1440             .setGroup(1);
1441 
1442     page->addWidget(new CVarToggleWidget("hud-unhide-pickup-invitem"))
1443             .setRight()
1444             .setGroup(1);
1445 #endif // __JHERETIC__ || __JHEXEN__
1446 
1447 #endif // __JDOOM__ || __JHERETIC__ || __JHEXEN__
1448 
1449 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
1450     page->addWidget(new LabelWidget("Counters"))
1451             .setGroup(5)
1452             .setColor(MENU_COLOR2);
1453 
1454     page->addWidget(new LabelWidget("Items"))
1455             .setLeft()
1456             .setGroup(5);
1457     page->addWidget(new CVarInlineListWidget("hud-cheat-counter", CCH_ITEMS | CCH_ITEMS_PRCNT))
1458             .addItems(ListWidget::Items() << new ListWidgetItem("Hidden",        0)
1459                                           << new ListWidgetItem("Count",         CCH_ITEMS)
1460                                           << new ListWidgetItem("Percent",       CCH_ITEMS_PRCNT)
1461                                           << new ListWidgetItem("Count+Percent", CCH_ITEMS | CCH_ITEMS_PRCNT))
1462             .setRight()
1463             .setGroup(5)
1464             .setShortcut('i');
1465 
1466     page->addWidget(new LabelWidget("Kills"))
1467             .setLeft()
1468             .setGroup(5);
1469 
1470     page->addWidget(new CVarInlineListWidget("hud-cheat-counter", CCH_KILLS | CCH_KILLS_PRCNT))
1471             .addItems(ListWidget::Items() << new ListWidgetItem("Hidden",        0)
1472                                           << new ListWidgetItem("Count",         CCH_KILLS)
1473                                           << new ListWidgetItem("Percent",       CCH_KILLS_PRCNT)
1474                                           << new ListWidgetItem("Count+Percent", CCH_KILLS | CCH_KILLS_PRCNT))
1475             .setRight()
1476             .setGroup(5)
1477             .setShortcut('k');
1478 
1479     page->addWidget(new LabelWidget("Secrets"))
1480             .setLeft()
1481             .setGroup(5);
1482     page->addWidget(new CVarInlineListWidget("hud-cheat-counter", CCH_SECRETS | CCH_SECRETS_PRCNT))
1483             .addItems(ListWidget::Items() << new ListWidgetItem("Hidden",        0)
1484                                           << new ListWidgetItem("Count",         CCH_SECRETS)
1485                                           << new ListWidgetItem("Percent",       CCH_SECRETS_PRCNT)
1486                                           << new ListWidgetItem("Count+Percent", CCH_SECRETS | CCH_SECRETS_PRCNT))
1487             .setGroup(5)
1488             .setRight()
1489             .setShortcut('s');
1490 
1491     page->addWidget(new LabelWidget("Automap Only"))
1492             .setLeft()
1493             .setGroup(5);
1494     page->addWidget(new CVarToggleWidget("hud-cheat-counter-show-mapopen"))
1495             .setRight()
1496             .setGroup(5);
1497 
1498     page->addWidget(new LabelWidget("Size"))
1499             .setLeft()
1500             .setGroup(5);
1501     page->addWidget(new CVarSliderWidget("hud-cheat-counter-scale"))
1502             .setRight()
1503             .setGroup(5);
1504 
1505 #endif // __JDOOM__ || __JDOOM64__ || __JHERETIC__
1506 
1507     page->addWidget(new LabelWidget("Fullscreen"))
1508             .setGroup(6)
1509             .setColor(MENU_COLOR2);
1510 
1511     page->addWidget(new LabelWidget("Size"))
1512             .setLeft()
1513             .setGroup(6);
1514     page->addWidget(new CVarSliderWidget("hud-scale"))
1515             .setRight()
1516             .setGroup(6);
1517 
1518     page->addWidget(new LabelWidget("Text Color"))
1519             .setLeft()
1520             .setGroup(6);
1521     page->addWidget(new CVarColorEditWidget("hud-color-r", "hud-color-g", "hud-color-b", "hud-color-a", Vector4f(), true))
1522             .setRight()
1523             .setGroup(6)
1524             .setAction(Widget::Deactivated, CVarColorEditWidget_UpdateCVar)
1525             .setAction(Widget::Activated,   Hu_MenuActivateColorWidget);
1526 
1527 #if __JHEXEN__
1528 
1529     page->addWidget(new LabelWidget("Show Mana"))
1530             .setLeft()
1531             .setGroup(6);
1532 
1533     page->addWidget(new CVarToggleWidget("hud-mana"))
1534             .setRight()
1535             .setGroup(6);
1536 
1537 #endif // __JHEXEN__
1538 
1539 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
1540 
1541     page->addWidget(new LabelWidget("Show Ammo"))
1542             .setLeft()
1543             .setGroup(6);
1544     page->addWidget(new CVarToggleWidget("hud-ammo"))
1545             .setRight()
1546             .setGroup(6)
1547             .setShortcut('a');
1548 
1549     page->addWidget(new LabelWidget("Show Armor"))
1550             .setLeft()
1551             .setGroup(6);
1552     page->addWidget(new CVarToggleWidget("hud-armor"))
1553             .setRight()
1554             .setGroup(6)
1555             .setShortcut('r');
1556 
1557 #endif // __JDOOM__ || __JDOOM64__ || __JHERETIC__
1558 
1559 #if __JDOOM64__
1560 
1561     page->addWidget(new LabelWidget("Show PowerKeys"))
1562             .setLeft()
1563             .setGroup(6);
1564     page->addWidget(new CVarToggleWidget("hud-power"))
1565             .setRight()
1566             .setGroup(6)
1567             .setShortcut('p');
1568 
1569 #endif // __JDOOM64__
1570 
1571 #if __JDOOM__
1572 
1573     page->addWidget(new LabelWidget("Show Status"))
1574             .setLeft()
1575             .setGroup(6);
1576     page->addWidget(new CVarToggleWidget("hud-face"))
1577             .setRight()
1578             .setGroup(6)
1579             .setShortcut('f');
1580 
1581 #endif // __JDOOM__
1582 
1583     page->addWidget(new LabelWidget("Show Health"))
1584             .setLeft()
1585             .setGroup(6);
1586     page->addWidget(new CVarToggleWidget("hud-health"))
1587             .setRight()
1588             .setGroup(6)
1589             .setShortcut('h');
1590 
1591 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
1592 
1593     page->addWidget(new LabelWidget("Show Keys"))
1594             .setLeft()
1595             .setGroup(6);
1596     page->addWidget(new CVarToggleWidget("hud-keys"))
1597             .setRight()
1598             .setGroup(6);
1599 
1600 #endif // __JDOOM__ || __JDOOM64__ || __JHERETIC__
1601 
1602 #if __JHERETIC__ || __JHEXEN__
1603 
1604     page->addWidget(new LabelWidget("Show Ready-Item"))
1605             .setLeft()
1606             .setGroup(6);
1607     page->addWidget(new CVarToggleWidget("hud-currentitem"))
1608             .setRight()
1609             .setGroup(6);
1610 
1611 #endif // __JHERETIC__ || __JHEXEN__
1612 }
1613 
Hu_MenuInitAutomapOptionsPage()1614 void Hu_MenuInitAutomapOptionsPage()
1615 {
1616 #if __JHERETIC__ || __JHEXEN__
1617     const Vector2i origin(32, 28);
1618 #else
1619     const Vector2i origin(70, 40);
1620 #endif
1621 
1622     Page *page = Hu_MenuAddPage(new Page("AutomapOptions", origin));
1623     page->setLeftColumnWidth(.55f);
1624     page->setTitle("Automap Options");
1625     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTA));
1626     page->setPreviousPage(Hu_MenuPagePtr("Options"));
1627 
1628     page->addWidget(new LabelWidget("Rotation"))
1629             .setLeft();
1630     {
1631         auto *tgl = new CVarToggleWidget("map-rotate");
1632         tgl->setRight();
1633         tgl->setShortcut('r');
1634         tgl->setStateChangeCallback([](CVarToggleWidget::State state) {
1635             G_SetAutomapRotateMode(state == CVarToggleWidget::Down);
1636         });
1637         page->addWidget(tgl);
1638     }
1639 
1640     page->addWidget(new LabelWidget("Always Update Map"))
1641             .setLeft();
1642     page->addWidget(new CVarToggleWidget("map-neverobscure"))
1643             .setRight()
1644             .setShortcut('a')
1645             .setHelpInfo("Update map even when background is opaque");
1646 
1647 #if !defined (__JDOOM64__)
1648     page->addWidget(new LabelWidget("HUD Display"))
1649             .setLeft();
1650     page->addWidget(new CVarInlineListWidget("map-huddisplay"))
1651             .addItems(ListWidget::Items() << new ListWidgetItem("None",      0)
1652                                           << new ListWidgetItem("Current",   1)
1653                                           << new ListWidgetItem("Statusbar", 2))
1654             .setRight()
1655             .setShortcut('h');
1656 #endif
1657 
1658     page->addWidget(new LabelWidget("Appearance"))
1659             .setGroup(1)
1660             .setColor(MENU_COLOR2);
1661 
1662     page->addWidget(new LabelWidget("Background Opacity"))
1663             .setLeft()
1664             .setGroup(1);
1665     page->addWidget(new CVarSliderWidget("map-opacity"))
1666             .setShortcut('o')
1667             .setRight()
1668             .setGroup(1);
1669 
1670     page->addWidget(new LabelWidget("Line Opacity"))
1671             .setLeft()
1672             .setGroup(1);
1673     page->addWidget(new CVarSliderWidget("map-line-opacity"))
1674             .setShortcut('l')
1675             .setRight()
1676             .setGroup(1);
1677 
1678     page->addWidget(new LabelWidget("Line Width"))
1679             .setLeft()
1680             .setGroup(1);
1681     page->addWidget(new CVarSliderWidget("map-line-width", 0.5f, 8.f))
1682             .setRight()
1683             .setGroup(1);
1684 
1685     page->addWidget(new LabelWidget("Colored Doors"))
1686             .setLeft()
1687             .setGroup(1);
1688     page->addWidget(new CVarToggleWidget("map-door-colors"))
1689             .setRight()
1690             .setShortcut('d')
1691             .setGroup(1);
1692 
1693     page->addWidget(new LabelWidget("Door Glow"))
1694             .setLeft()
1695             .setGroup(1);
1696     page->addWidget(new CVarSliderWidget("map-door-glow", 0, 200, 5))
1697             .setRight()
1698             .setShortcut('g')
1699             .setGroup(1);
1700 
1701     page->addWidget(new LabelWidget("Use Custom Colors"))
1702             .setLeft()
1703             .setGroup(2);
1704     page->addWidget(new CVarInlineListWidget("map-customcolors"))
1705             .addItems(ListWidget::Items() << new ListWidgetItem("Never",  0)
1706                                           << new ListWidgetItem("Auto",   1)
1707                                           << new ListWidgetItem("Always", 2))
1708             .setRight()
1709             .setGroup(2);
1710 
1711     page->addWidget(new LabelWidget("Wall"))
1712             .setLeft()
1713             .setGroup(2);
1714     page->addWidget(new CVarColorEditWidget("map-wall-r", "map-wall-g", "map-wall-b"))
1715             .setRight()
1716             .setShortcut('w')
1717             .setGroup(2)
1718             .setAction(Widget::Activated, Hu_MenuActivateColorWidget);
1719 
1720     page->addWidget(new LabelWidget("Floor Height Change"))
1721             .setLeft()
1722             .setGroup(2);
1723     page->addWidget(new CVarColorEditWidget("map-wall-floorchange-r", "map-wall-floorchange-g", "map-wall-floorchange-b"))
1724             .setRight()
1725             .setShortcut('f')
1726             .setGroup(2)
1727             .setAction(Widget::Activated, Hu_MenuActivateColorWidget);
1728 
1729     page->addWidget(new LabelWidget("Ceiling Height Change"))
1730             .setLeft()
1731             .setGroup(2);
1732     page->addWidget(new CVarColorEditWidget("map-wall-ceilingchange-r", "map-wall-ceilingchange-g", "map-wall-ceilingchange-b"))
1733             .setRight()
1734             .setGroup(2)
1735             .setAction(Widget::Activated, Hu_MenuActivateColorWidget);
1736 
1737     page->addWidget(new LabelWidget("Unseen"))
1738             .setLeft()
1739             .setGroup(2);
1740     page->addWidget(new CVarColorEditWidget("map-wall-unseen-r", "map-wall-unseen-g", "map-wall-unseen-b"))
1741             .setRight()
1742             .setGroup(2)
1743             .setShortcut('u')
1744             .setAction(Widget::Activated, Hu_MenuActivateColorWidget);
1745 
1746     page->addWidget(new LabelWidget("Thing"))
1747             .setLeft()
1748             .setGroup(2);
1749     page->addWidget(new CVarColorEditWidget("map-mobj-r", "map-mobj-g", "map-mobj-b"))
1750             .setRight()
1751             .setGroup(2)
1752             .setShortcut('t')
1753             .setAction(Widget::Activated, Hu_MenuActivateColorWidget);
1754 
1755     page->addWidget(new LabelWidget("Background"))
1756             .setLeft()
1757             .setGroup(2);
1758     page->addWidget(new CVarColorEditWidget("map-background-r", "map-background-g", "map-background-b"))
1759             .setRight()
1760             .setGroup(2)
1761             .setShortcut('b')
1762             .setAction(Widget::Activated, Hu_MenuActivateColorWidget);
1763 }
1764 
compareWeaponPriority(ListWidgetItem const * a,ListWidgetItem const * b)1765 static bool compareWeaponPriority(ListWidgetItem const *a, ListWidgetItem const *b)
1766 {
1767     int i = 0, aIndex = -1, bIndex = -1;
1768     do
1769     {
1770         if (cfg.common.weaponOrder[i] == a->userValue())
1771         {
1772             aIndex = i;
1773         }
1774         if (cfg.common.weaponOrder[i] == b->userValue())
1775         {
1776             bIndex = i;
1777         }
1778     } while(!(aIndex != -1 && bIndex != -1) && ++i < NUM_WEAPON_TYPES);
1779 
1780     return aIndex < bIndex;
1781 }
1782 
Hu_MenuInitWeaponsPage()1783 void Hu_MenuInitWeaponsPage()
1784 {
1785 #if __JDOOM__ || __JDOOM64__
1786     Vector2i const origin(78, 40);
1787 #elif __JHERETIC__
1788     Vector2i const origin(78, 26);
1789 #elif __JHEXEN__
1790     Vector2i const origin(78, 38);
1791 #endif
1792 
1793     const struct {
1794         char const *text;
1795         weapontype_t data;
1796     } weaponOrder[NUM_WEAPON_TYPES+1] = {
1797 #if __JDOOM__ || __JDOOM64__
1798         { (char const *)TXT_WEAPON1,             WT_FIRST },
1799         { (char const *)TXT_WEAPON2,             WT_SECOND },
1800         { (char const *)TXT_WEAPON3,             WT_THIRD },
1801         { (char const *)TXT_WEAPON4,             WT_FOURTH },
1802         { (char const *)TXT_WEAPON5,             WT_FIFTH },
1803         { (char const *)TXT_WEAPON6,             WT_SIXTH },
1804         { (char const *)TXT_WEAPON7,             WT_SEVENTH },
1805         { (char const *)TXT_WEAPON8,             WT_EIGHTH },
1806         { (char const *)TXT_WEAPON9,             WT_NINETH },
1807 #  if __JDOOM64__
1808         { (char const *)TXT_WEAPON10,            WT_TENTH },
1809 #  endif
1810 #elif __JHERETIC__
1811         { (char const *)TXT_TXT_WPNSTAFF,        WT_FIRST },
1812         { (char const *)TXT_TXT_WPNWAND,         WT_SECOND },
1813         { (char const *)TXT_TXT_WPNCROSSBOW,     WT_THIRD },
1814         { (char const *)TXT_TXT_WPNBLASTER,      WT_FOURTH },
1815         { (char const *)TXT_TXT_WPNSKULLROD,     WT_FIFTH },
1816         { (char const *)TXT_TXT_WPNPHOENIXROD,   WT_SIXTH },
1817         { (char const *)TXT_TXT_WPNMACE,         WT_SEVENTH },
1818         { (char const *)TXT_TXT_WPNGAUNTLETS,    WT_EIGHTH },
1819 #elif __JHEXEN__
1820         /// @todo We should allow different weapon preferences per player-class.
1821         { "First",  WT_FIRST },
1822         { "Second", WT_SECOND },
1823         { "Third",  WT_THIRD },
1824         { "Fourth", WT_FOURTH },
1825 #endif
1826         { "", WT_NOCHANGE}
1827     };
1828 
1829     Page *page = Hu_MenuAddPage(new Page("WeaponOptions", origin));
1830     page->setLeftColumnWidth(.5f);
1831     page->setTitle("Weapons Options");
1832     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTA));
1833     page->setPreviousPage(Hu_MenuPagePtr("Options"));
1834 
1835     page->addWidget(new LabelWidget("Priority Order"))
1836             .setColor(MENU_COLOR2);
1837 
1838     ListWidget::Items weapItems;
1839     for (int i = 0; weaponOrder[i].data < NUM_WEAPON_TYPES; ++i)
1840     {
1841         char const *itemText = weaponOrder[i].text;
1842         if(itemText && (PTR2INT(itemText) > 0 && PTR2INT(itemText) < NUMTEXT))
1843         {
1844             itemText = GET_TXT(PTR2INT(itemText));
1845         }
1846         weapItems << new ListWidgetItem(itemText, weaponOrder[i].data);
1847     }
1848     qSort(weapItems.begin(), weapItems.end(), compareWeaponPriority);
1849     page->addWidget(new ListWidget)
1850             .addItems(weapItems)
1851             .setReorderingEnabled(true)
1852             .setHelpInfo("Use left/right to move weapon up/down")
1853             .setShortcut('p')
1854             .setColor(MENU_COLOR3)
1855             .setAction(Widget::Modified,    Hu_MenuChangeWeaponPriority)
1856             .setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
1857 
1858     page->addWidget(new LabelWidget("Cycling"))
1859             .setGroup(1)
1860             .setColor(MENU_COLOR2);
1861 
1862     page->addWidget(new LabelWidget("Use Priority Order"))
1863             .setLeft()
1864             .setGroup(1);
1865     page->addWidget(new CVarToggleWidget("player-weapon-nextmode"))
1866             .setRight()
1867             .setGroup(1)
1868             .setShortcut('o');
1869 
1870     page->addWidget(new LabelWidget("Sequential"))
1871             .setLeft()
1872             .setGroup(1);
1873     page->addWidget(new CVarToggleWidget("player-weapon-cycle-sequential"))
1874             .setRight()
1875             .setGroup(1)
1876             .setShortcut('s');
1877 
1878     page->addWidget(new LabelWidget("Autoswitch"))
1879             .setGroup(2)
1880             .setColor(MENU_COLOR2);
1881 
1882     page->addWidget(new LabelWidget("Pickup Weapon"))
1883             .setLeft()
1884             .setGroup(2);
1885     page->addWidget(new CVarInlineListWidget("player-autoswitch"))
1886             .addItems(ListWidget::Items() << new ListWidgetItem("Never",     0)
1887                                           << new ListWidgetItem("If Better", 1)
1888                                           << new ListWidgetItem("Always",    2))
1889             .setGroup(2)
1890             .setRight()
1891             .setShortcut('w');
1892 
1893     page->addWidget(new LabelWidget("   If Not Firing"))
1894             .setLeft()
1895             .setGroup(2);
1896     page->addWidget(new CVarToggleWidget("player-autoswitch-notfiring"))
1897             .setRight()
1898             .setGroup(2)
1899             .setShortcut('f');
1900 
1901     page->addWidget(new LabelWidget("Pickup Ammo"))
1902             .setLeft()
1903             .setGroup(2);
1904     page->addWidget(new CVarInlineListWidget("player-autoswitch-ammo"))
1905             .addItems(ListWidget::Items() << new ListWidgetItem("Never",     0)
1906                                           << new ListWidgetItem("If Better", 1)
1907                                           << new ListWidgetItem("Always",    2))
1908             .setGroup(2)
1909             .setRight()
1910             .setShortcut('a');
1911 
1912 #if __JDOOM__ || __JDOOM64__
1913 
1914     page->addWidget(new LabelWidget("Pickup Beserk"))
1915             .setLeft()
1916             .setGroup(2);
1917     page->addWidget(new CVarToggleWidget("player-autoswitch-berserk"))
1918             .setRight()
1919             .setGroup(2)
1920             .setShortcut('b');
1921 
1922 #endif
1923 }
1924 
1925 #if __JHERETIC__ || __JHEXEN__
Hu_MenuInitInventoryOptionsPage()1926 void Hu_MenuInitInventoryOptionsPage()
1927 {
1928     Page *page = Hu_MenuAddPage(new Page("InventoryOptions", Vector2i(78, 48)));
1929     page->setLeftColumnWidth(.65f);
1930     page->setTitle("Inventory Options");
1931     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTA));
1932     page->setPreviousPage(Hu_MenuPagePtr("Options"));
1933 
1934     page->addWidget(new LabelWidget("Select Mode"))
1935             .setLeft();
1936     page->addWidget(new CVarToggleWidget("ctl-inventory-mode", 0, "Scroll", "Cursor"))
1937             .setRight()
1938             .setShortcut('s');
1939 
1940     page->addWidget(new LabelWidget("Wrap Around"))
1941             .setLeft();
1942     page->addWidget(new CVarToggleWidget("ctl-inventory-wrap"))
1943             .setRight()
1944             .setShortcut('w');
1945 
1946     page->addWidget(new LabelWidget("Choose And Use"))
1947             .setLeft();
1948     page->addWidget(new CVarToggleWidget("ctl-inventory-use-immediate"))
1949             .setRight()
1950             .setShortcut('c');
1951 
1952     page->addWidget(new LabelWidget("Select Next If Use Failed"))
1953             .setLeft();
1954     page->addWidget(new CVarToggleWidget("ctl-inventory-use-next"))
1955             .setRight()
1956             .setShortcut('n');
1957 
1958     page->addWidget(new LabelWidget("AutoHide"))
1959             .setLeft();
1960     page->addWidget(new CVarTextualSliderWidget("hud-inventory-timer", 0, 30, 1.f))
1961             .setEmptyText("Disabled")
1962             .setOnethSuffix(" second")
1963             .setNthSuffix(" seconds")
1964             .setShortcut('h')
1965             .setRight();
1966 
1967     page->addWidget(new LabelWidget("Fullscreen HUD"))
1968             .setGroup(1)
1969             .setColor(MENU_COLOR2);
1970 
1971     page->addWidget(new LabelWidget("Max Visible Slots"))
1972             .setLeft()
1973             .setGroup(1);
1974 
1975     page->addWidget(new CVarTextualSliderWidget("hud-inventory-slot-max", 0, 16, 1, false))
1976             .setEmptyText("Automatic")
1977             .setRight()
1978             .setGroup(1)
1979             .setShortcut('v');
1980 
1981     page->addWidget(new LabelWidget("Show Empty Slots"))
1982             .setGroup(1)
1983             .setLeft();
1984 
1985     page->addWidget(new CVarToggleWidget("hud-inventory-slot-showempty"))
1986             .setGroup(1)
1987             .setRight()
1988             .setShortcut('e');
1989 }
1990 #endif
1991 
Hu_MenuInitSoundOptionsPage()1992 void Hu_MenuInitSoundOptionsPage()
1993 {
1994 //#if __JHEXEN__
1995 //    Vector2i const origin(97, 25);
1996 //#elif __JHERETIC__
1997 //    Vector2i const origin(97, 30);
1998 //#elif __JDOOM__ || __JDOOM64__
1999     Vector2i const origin(97, 40);
2000 //#endif
2001 
2002     Page *page = Hu_MenuAddPage(new Page("SoundOptions", origin));
2003     page->setLeftColumnWidth(.4f);
2004     page->setTitle("Sound Options");
2005     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTA));
2006     page->setPreviousPage(Hu_MenuPagePtr("Options"));
2007 
2008     page->addWidget(new LabelWidget("SFX Volume"))
2009             .setLeft();
2010     page->addWidget(new CVarSliderWidget("sound-volume", 0, 255, 16, false))
2011             .setRight()
2012             .setShortcut('s');
2013 
2014     page->addWidget(new LabelWidget("Music Volume"))
2015             .setLeft();
2016     page->addWidget(new CVarSliderWidget("music-volume", 0, 255, 16, false))
2017         .setRight()
2018         .setShortcut('m');
2019 }
2020 
2021 /**
2022  * Construct the episode selection menu.
2023  */
Hu_MenuInitEpisodePage()2024 void Hu_MenuInitEpisodePage()
2025 {
2026 #if __JHEXEN__
2027     Vector2i const origin(120, 44);
2028 #elif __JHERETIC__
2029     Vector2i const origin(80, 50);
2030 #else
2031     Vector2i const origin(48, 63);
2032 #endif
2033 
2034     Page *page =
2035         Hu_MenuAddPage(new Page("Episode", origin, Page::FixedLayout, Hu_MenuDrawEpisodePage));
2036 
2037     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTB));
2038     page->setPreviousPage(Hu_MenuPagePtr("Main"));
2039     page->setOnActiveCallback([](Page &page) {
2040         const auto &items = page.children();
2041         if (items.size() == 1)
2042         {
2043             // If there is only one episode, select it automatically.
2044             auto &ep = items.front()->as<ButtonWidget>();
2045             ep.setSilent(true);
2046             ep.handleCommand(MCMD_SELECT);
2047             ep.setSilent(false);
2048         }
2049     });
2050 
2051     const DictionaryValue::Elements &episodesById = Defs().episodes.lookup("id").elements();
2052     if (!episodesById.size())
2053     {
2054         LOG_WARNING(
2055             "No episodes are defined. It will not be possible to start a new game from the menu");
2056         return;
2057     }
2058 
2059     int y = 0;
2060     int n = 0;
2061     for (auto const &pair : episodesById)
2062     {
2063         const Record &episodeDef   = *pair.second->as<RecordValue>().record();
2064         const String  episodeId    = episodeDef.gets("id");
2065         const String  episodeTitle = G_EpisodeTitle(episodeId);
2066 
2067         if (episodeTitle.empty())
2068         {
2069             // Hidden/untitled episode.
2070             continue;
2071         }
2072 
2073         auto *btn = new ButtonWidget(episodeTitle);
2074         btn->setFixedY(y);
2075 
2076         // Has a menu image been specified?
2077         de::Uri image(episodeDef.gets("menuImage"), RC_NULL);
2078         if (!image.path().isEmpty())
2079         {
2080             // Presently only patches are supported.
2081             if (!image.scheme().compareWithoutCase("Patches"))
2082             {
2083                 btn->setPatch(R_DeclarePatch(image.path().toUtf8().constData()));
2084             }
2085         }
2086 
2087         // Has a menu shortcut/hotkey been specified?
2088         /// @todo Validate symbolic dday key names.
2089         String const shortcut = episodeDef.gets("menuShortcut");
2090         if (!shortcut.isEmpty() && shortcut.first().isLetterOrNumber())
2091         {
2092             btn->setShortcut(shortcut.first().toLower().toLatin1());
2093         }
2094 
2095         // Has a menu help/info text been specified?
2096         String const helpInfo = episodeDef.gets("menuHelpInfo");
2097         if (!helpInfo.isEmpty())
2098         {
2099             btn->setHelpInfo(helpInfo);
2100         }
2101 
2102         de::Uri startMap(episodeDef.gets("startMap"), RC_NULL);
2103         if (P_MapExists(startMap.compose().toUtf8().constData()))
2104         {
2105             btn->setAction(Widget::Deactivated, Hu_MenuSelectEpisode);
2106             btn->setUserValue(episodeId);
2107         }
2108         else
2109         {
2110 #if __JDOOM__ || __JHERETIC__
2111             // In shareware display a prompt to buy the full game.
2112             if (
2113 #    if __JHERETIC__
2114                 gameMode == heretic_shareware
2115 #    else // __JDOOM__
2116                 gameMode == doom_shareware
2117 #    endif
2118                 && startMap.path() != "E1M1")
2119             {
2120                 btn->setAction(Widget::Deactivated, Hu_MenuActivateNotSharewareEpisode);
2121             }
2122             else
2123 #endif
2124             {
2125                 // Disable this selection and log a warning for the mod author.
2126                 btn->setFlags(Widget::Disabled);
2127                 LOG_RES_WARNING("Failed to locate the starting map \"%s\" for episode '%s'."
2128                                 " This episode will not be selectable from the menu")
2129                     << startMap << episodeId;
2130             }
2131         }
2132 
2133         btn->setAction(Widget::FocusGained, Hu_MenuDefaultFocusAction);
2134         btn->setFont(MENU_FONT1);
2135         page->addWidget(btn);
2136 
2137         y += FIXED_LINE_HEIGHT;
2138         n += 1;
2139     }
2140 }
2141 
2142 #if __JHEXEN__
2143 /**
2144  * Construct the player class selection menu.
2145  */
Hu_MenuInitPlayerClassPage()2146 void Hu_MenuInitPlayerClassPage()
2147 {
2148     // First determine the number of selectable player classes.
2149     int count = 0;
2150     for(int i = 0; i < NUM_PLAYER_CLASSES; ++i)
2151     {
2152         classinfo_t *info = PCLASS_INFO(i);
2153         if(info->userSelectable)
2154         {
2155             ++count;
2156         }
2157     }
2158 
2159     Page *page = Hu_MenuAddPage(new Page("PlayerClass", Vector2i(66, 66), Page::FixedLayout | Page::NoScroll,
2160                                          Hu_MenuDrawPlayerClassPage, Hu_MenuSkipPreviousPageIfSkippingEpisodeSelection));
2161     page->setPredefinedFont(MENU_FONT1, FID(GF_FONTB));
2162     page->setPreviousPage(Hu_MenuPagePtr("Episode"));
2163 
2164     uint y = 0;
2165 
2166     // Add the selectable classes.
2167     int n = 0;
2168     while(n < count)
2169     {
2170         classinfo_t *info = PCLASS_INFO(n++);
2171 
2172         if(!info->userSelectable) continue;
2173 
2174         String text;
2175         if(info->niceName && (PTR2INT(info->niceName) > 0 && PTR2INT(info->niceName) < NUMTEXT))
2176         {
2177             text = String(GET_TXT(PTR2INT(info->niceName)));
2178         }
2179         else
2180         {
2181             text = String(info->niceName);
2182         }
2183 
2184         auto *btn = new ButtonWidget(text);
2185 
2186         if(!btn->text().isEmpty() && btn->text().first().isLetterOrNumber()) btn->setShortcut(btn->text().first().toLatin1());
2187         btn->setFixedY(y);
2188         btn->setAction(Widget::Deactivated, Hu_MenuSelectPlayerClass);
2189         btn->setAction(Widget::FocusGained, Hu_MenuFocusOnPlayerClass);
2190         btn->setUserValue2(int(info->plrClass));
2191         btn->setFont(MENU_FONT1);
2192 
2193         page->addWidget(btn);
2194         y += FIXED_LINE_HEIGHT;
2195     }
2196 
2197     // Random class button.
2198     String const labelText = GET_TXT(TXT_RANDOMPLAYERCLASS);
2199     int const shortcut     = labelText.first().isLetterOrNumber()? labelText.first().toLatin1() : 0;
2200     page->addWidget(new ButtonWidget(labelText))
2201             .setFixedY(y)
2202             .setShortcut(shortcut)
2203             .setUserValue2(int(PCLASS_NONE))
2204             .setFont(MENU_FONT1)
2205             .setColor(MENU_COLOR1)
2206             .setAction(Widget::Deactivated, Hu_MenuSelectPlayerClass)
2207             .setAction(Widget::FocusGained, Hu_MenuFocusOnPlayerClass);
2208 
2209     // Mobj preview background.
2210     page->addWidget(new RectWidget)
2211             .setFlags(Widget::NoFocus | Widget::Id1)
2212             .setFixedOrigin(Vector2i(108, -58))
2213             .setOnTickCallback(Hu_MenuPlayerClassBackgroundTicker);
2214 
2215     // Mobj preview.
2216     page->addWidget(new MobjPreviewWidget)
2217             .setFlags(Widget::Id0)
2218             .setFixedOrigin(Vector2i(108 + 55, -58 + 76))
2219             .setOnTickCallback(Hu_MenuPlayerClassPreviewTicker);
2220 }
2221 #endif
2222 
Hu_MenuAddPage(Page * page)2223 Page *Hu_MenuAddPage(Page *page)
2224 {
2225     if(!page) return page;
2226 
2227     // Have we already added this page?
2228     for(Page *other : pages)
2229     {
2230         if(other == page) return page;
2231     }
2232 
2233     // Is the name valid?
2234     String nameInIndex = page->name().toLower();
2235     if(nameInIndex.isEmpty())
2236     {
2237         throw Error("Hu_MenuPage", "A page must have a valid (i.e., not empty) name");
2238     }
2239 
2240     // Is the name unique?
2241     if(pages.contains(nameInIndex))
2242     {
2243         throw Error("Hu_MenuPage", "A page with the name '" + page->name() + "' is already present");
2244     }
2245 
2246     pages.insert(nameInIndex, page);
2247     return page;
2248 }
2249 
2250 /// @note Called during (post-engine) init and after updating game/engine state.
Hu_MenuInit()2251 void Hu_MenuInit()
2252 {
2253     // Close the menu (if open) and shutdown (if initialized - we're reinitializing).
2254     Hu_MenuShutdown();
2255 
2256     mnAlpha = mnTargetAlpha = 0;
2257     currentPage = 0;
2258     menuActive  = false;
2259 
2260     cursor.hasRotation = false;
2261     cursor.angle       = 0;
2262     cursor.animFrame   = 0;
2263     cursor.animCounter = MENU_CURSOR_TICSPERFRAME;
2264 
2265     DD_Execute(true, "deactivatebcontext menu");
2266 
2267     Hu_MenuLoadResources();
2268 
2269     initAllPages();
2270 
2271 #if __JDOOM__
2272     if(gameModeBits & GM_ANY_DOOM2)
2273     {
2274         Page &mainPage = Hu_MenuPage("Main");
2275 
2276         Widget &wiReadThis = mainPage.findWidget(Widget::Id0);
2277         wiReadThis.setFlags(Widget::Disabled | Widget::Hidden | Widget::NoFocus);
2278 
2279         Widget &wiQuitGame = mainPage.findWidget(Widget::Id1);
2280         wiQuitGame.setFixedY(wiQuitGame.fixedY() - FIXED_LINE_HEIGHT);
2281     }
2282 #endif
2283 
2284     inited = true;
2285 }
2286 
Hu_MenuShutdown()2287 void Hu_MenuShutdown()
2288 {
2289     if(!inited) return;
2290 
2291     Hu_MenuCommand(MCMD_CLOSEFAST);
2292     destroyAllPages();
2293     inited = false;
2294 }
2295 
Hu_MenuIsActive()2296 bool Hu_MenuIsActive()
2297 {
2298     return menuActive;
2299 }
2300 
Hu_MenuSetOpacity(float alpha)2301 void Hu_MenuSetOpacity(float alpha)
2302 {
2303     // The menu's alpha will start moving towards this target value.
2304     mnTargetAlpha = alpha;
2305 }
2306 
Hu_MenuOpacity()2307 float Hu_MenuOpacity()
2308 {
2309     return mnAlpha;
2310 }
2311 
Hu_MenuTicker(timespan_t ticLength)2312 void Hu_MenuTicker(timespan_t ticLength)
2313 {
2314 #define MENUALPHA_FADE_STEP (.07f)
2315 
2316     // Move towards the target alpha level for the entire menu.
2317     float diff = mnTargetAlpha - mnAlpha;
2318     if(fabs(diff) > MENUALPHA_FADE_STEP)
2319     {
2320         mnAlpha += float( MENUALPHA_FADE_STEP * ticLength * TICRATE * (diff > 0? 1 : -1) );
2321     }
2322     else
2323     {
2324         mnAlpha = mnTargetAlpha;
2325     }
2326 
2327     if(!menuActive) return;
2328 
2329     // Animate cursor rotation?
2330     if(cfg.common.menuCursorRotate)
2331     {
2332         if(cursor.hasRotation)
2333         {
2334             cursor.angle += float( 5 * ticLength * TICRATE );
2335         }
2336         else if (!fequal(cursor.angle, 0))
2337         {
2338             float rewind = float( MENU_CURSOR_REWIND_SPEED * ticLength * TICRATE );
2339             if(cursor.angle <= rewind || cursor.angle >= 360 - rewind)
2340                 cursor.angle = 0;
2341             else if(cursor.angle < 180)
2342                 cursor.angle -= rewind;
2343             else
2344                 cursor.angle += rewind;
2345         }
2346 
2347         if(cursor.angle >= 360)
2348             cursor.angle -= 360;
2349     }
2350 
2351     // Time to think? Updates on 35Hz game ticks.
2352     if(!DD_IsSharpTick()) return;
2353 
2354     // Advance menu time.
2355     menuTime++;
2356 
2357     // Animate the cursor graphic?
2358     if(--cursor.animCounter <= 0)
2359     {
2360         cursor.animFrame++;
2361         cursor.animCounter = MENU_CURSOR_TICSPERFRAME;
2362         if(cursor.animFrame > MENU_CURSOR_FRAMECOUNT-1)
2363             cursor.animFrame = 0;
2364     }
2365 
2366     // Used for Heretic's rotating skulls.
2367     frame = (menuTime / 3) % 18;
2368 
2369     // Call the active page's ticker.
2370     currentPage->tick();
2371 
2372 #undef MENUALPHA_FADE_STEP
2373 }
2374 
Hu_MenuHasPage()2375 bool Hu_MenuHasPage()
2376 {
2377     return currentPage != 0;
2378 }
2379 
Hu_MenuPage()2380 Page &Hu_MenuPage()
2381 {
2382     if(currentPage)
2383     {
2384         return *currentPage;
2385     }
2386     throw Error("Hu_MenuPage", "No current Page is presently configured");
2387 }
2388 
Hu_MenuSetPage(Page * page,bool canReactivate)2389 void Hu_MenuSetPage(Page *page, bool canReactivate)
2390 {
2391     if(!menuActive) return;
2392     if(!page) return;
2393 
2394     if(!Get(DD_NOVIDEO))
2395     {
2396         FR_ResetTypeinTimer();
2397     }
2398 
2399     cursor.angle = 0; // Stop cursor rotation animation dead (don't rewind).
2400     menuNominatingQuickSaveSlot = false;
2401 
2402     if(currentPage == page)
2403     {
2404         if(!canReactivate) return;
2405         page->setFocus(0);
2406     }
2407 
2408     // This is now the "active" page.
2409     currentPage = page;
2410     page->activate();
2411 }
2412 
Hu_MenuIsVisible()2413 bool Hu_MenuIsVisible()
2414 {
2415     return (menuActive || mnAlpha > .0001f);
2416 }
2417 
Hu_MenuDefaultFocusAction(Widget &,Widget::Action action)2418 void Hu_MenuDefaultFocusAction(Widget &, Widget::Action action)
2419 {
2420     if(action != Widget::FocusGained) return;
2421     Hu_MenuUpdateCursorState();
2422 }
2423 
Hu_MenuMergeEffectWithDrawTextFlags(short f)2424 short Hu_MenuMergeEffectWithDrawTextFlags(short f)
2425 {
2426     return ((~cfg.common.menuEffectFlags & DTF_NO_EFFECTS) | (f & ~DTF_NO_EFFECTS));
2427 }
2428 
Hu_MenuDrawFocusCursor(Vector2i const & origin,float scale,float alpha)2429 void Hu_MenuDrawFocusCursor(Vector2i const &origin, float scale, float alpha)
2430 {
2431 #if __JDOOM__ || __JDOOM64__
2432 # define OFFSET_X         (-22)
2433 # define OFFSET_Y         (-1)
2434 #elif __JHERETIC__ || __JHEXEN__
2435 # define OFFSET_X         (-16)
2436 # define OFFSET_Y         (1)
2437 #endif
2438 
2439     float const angle   = cursor.angle;
2440     int const cursorIdx = cursor.animFrame;
2441     patchid_t pCursor   = pCursors[cursorIdx % MENU_CURSOR_FRAMECOUNT];
2442 
2443     patchinfo_t info;
2444     if(!R_GetPatchInfo(pCursor, &info))
2445         return;
2446 
2447 //    float const scale = /*de::min((focusObjectHeight * 1.267f) /*/ 1; //info.geometry.size.height; //, 1.f);
2448     Vector2i pos = origin + Vector2i(OFFSET_X, OFFSET_Y) * scale;
2449 //    pos.y -= info.geometry.size.height / 2;
2450 
2451     DGL_MatrixMode(DGL_MODELVIEW);
2452     DGL_PushMatrix();
2453 
2454     DGL_Translatef(pos.x, pos.y, 0);
2455     DGL_Scalef(scale, scale, 1);
2456     DGL_Rotatef(angle, 0, 0, 1);
2457 
2458     DGL_Enable(DGL_TEXTURE_2D);
2459     DGL_Color4f(1, 1, 1, alpha);
2460 
2461     GL_DrawPatch(pCursor, Vector2i(0, 0), 0, DPF_NO_OFFSET);
2462 
2463     DGL_Disable(DGL_TEXTURE_2D);
2464 
2465     DGL_MatrixMode(DGL_MODELVIEW);
2466     DGL_PopMatrix();
2467 
2468 #undef OFFSET_Y
2469 #undef OFFSET_X
2470 }
2471 
Hu_MenuDrawPageTitle(String title,Vector2i const & origin)2472 void Hu_MenuDrawPageTitle(String title, Vector2i const &origin)
2473 {
2474     title = Widget::labelText(title);
2475 
2476     if(title.isEmpty()) return;
2477 
2478     DGL_Enable(DGL_TEXTURE_2D);
2479     FR_SetFont(FID(GF_FONTB));
2480     FR_SetColorv(cfg.common.menuTextColors[0]);
2481     FR_SetAlpha(mnRendState->pageAlpha);
2482 
2483     FR_DrawTextXY3(title.toLatin1(), origin.x, origin.y, ALIGN_TOP, Hu_MenuMergeEffectWithDrawTextFlags(0));
2484 
2485     DGL_Disable(DGL_TEXTURE_2D);
2486 }
2487 
Hu_MenuDrawPageHelp(String helpText,Vector2i const & origin)2488 void Hu_MenuDrawPageHelp(String helpText, Vector2i const &origin)
2489 {
2490     if(helpText.isEmpty()) return;
2491 
2492     DGL_MatrixMode(DGL_MODELVIEW);
2493     DGL_PushMatrix();
2494 
2495     DGL_Translatef(SCREENWIDTH / 2, SCREENHEIGHT, 0);
2496     DGL_Scalef(.666666f, .666666f, 1.f);
2497     DGL_Translatef(-SCREENWIDTH / 2, -SCREENHEIGHT, 0);
2498 
2499     DGL_Enable(DGL_TEXTURE_2D);
2500     FR_SetFont(FID(GF_FONTA));
2501     FR_SetColorv(cfg.common.menuTextColors[1]);
2502     FR_SetAlpha(mnRendState->pageAlpha);
2503 
2504     FR_DrawTextXY3(helpText.toLatin1(), origin.x, origin.y, ALIGN_BOTTOM, Hu_MenuMergeEffectWithDrawTextFlags(0));
2505 
2506     DGL_Disable(DGL_TEXTURE_2D);
2507 
2508     DGL_MatrixMode(DGL_MODELVIEW);
2509     DGL_PopMatrix();
2510 }
2511 
drawOverlayBackground(float darken)2512 static void drawOverlayBackground(float darken)
2513 {
2514     DGL_SetNoMaterial();
2515     DGL_DrawRectf2Color(0, 0, SCREENWIDTH, SCREENHEIGHT, 0, 0, 0, darken);
2516 }
2517 
beginOverlayDraw()2518 static void beginOverlayDraw()
2519 {
2520 #define SMALL_SCALE             .75f
2521 
2522     DGL_MatrixMode(DGL_MODELVIEW);
2523     DGL_PushMatrix();
2524 
2525     DGL_Translatef(SCREENWIDTH/2, SCREENHEIGHT/2, 0);
2526     DGL_Scalef(SMALL_SCALE, SMALL_SCALE, 1);
2527     DGL_Translatef(-(SCREENWIDTH/2), -(SCREENHEIGHT/2), 0);
2528 
2529 #undef SMALL_SCALE
2530 }
2531 
endOverlayDraw()2532 static void endOverlayDraw()
2533 {
2534     DGL_MatrixMode(DGL_MODELVIEW);
2535     DGL_PopMatrix();
2536 }
2537 
Hu_MenuDrawer()2538 void Hu_MenuDrawer()
2539 {
2540 #define OVERLAY_DARKEN          .7f
2541 
2542     dgl_borderedprojectionstate_t bp;
2543 
2544     if(!Hu_MenuIsVisible()) return;
2545 
2546     GL_ConfigureBorderedProjection(&bp, 0, SCREENWIDTH, SCREENHEIGHT,
2547         Get(DD_WINDOW_WIDTH), Get(DD_WINDOW_HEIGHT), scalemode_t(cfg.common.menuScaleMode));
2548     GL_BeginBorderedProjection(&bp);
2549 
2550     // First determine whether the focus cursor should be visible.
2551     Widget *focused = Hu_MenuPage().focusWidget();
2552     bool showFocusCursor = true;
2553     if(focused && focused->isActive())
2554     {
2555         if(is<ColorEditWidget>(focused) || is<InputBindingWidget>(focused))
2556         {
2557             showFocusCursor = false;
2558         }
2559     }
2560 
2561     DGL_MatrixMode(DGL_MODELVIEW);
2562     DGL_PushMatrix();
2563 
2564     DGL_Translatef(SCREENWIDTH/2, SCREENHEIGHT/2, 0);
2565     DGL_Scalef(cfg.common.menuScale, cfg.common.menuScale, 1);
2566     DGL_Translatef(-(SCREENWIDTH/2), -(SCREENHEIGHT/2), 0);
2567 
2568     Hu_MenuPage().draw(mnAlpha, showFocusCursor);
2569 
2570     DGL_MatrixMode(DGL_MODELVIEW);
2571     DGL_PopMatrix();
2572 
2573     GL_EndBorderedProjection(&bp);
2574 
2575     // Drawing any overlays?
2576     if(focused && focused->isActive())
2577     {
2578         if(is<ColorEditWidget>(focused))
2579         {
2580             drawOverlayBackground(OVERLAY_DARKEN);
2581             GL_BeginBorderedProjection(&bp);
2582 
2583             beginOverlayDraw();
2584                 Hu_MenuPage("ColorWidget").draw();
2585             endOverlayDraw();
2586 
2587             GL_EndBorderedProjection(&bp);
2588         }
2589         if(InputBindingWidget *binds = maybeAs<InputBindingWidget>(focused))
2590         {
2591             drawOverlayBackground(OVERLAY_DARKEN);
2592             GL_BeginBorderedProjection(&bp);
2593 
2594             beginOverlayDraw();
2595                 Hu_MenuControlGrabDrawer(binds->controlName(), 1);
2596             endOverlayDraw();
2597 
2598             GL_EndBorderedProjection(&bp);
2599         }
2600     }
2601 
2602 #undef OVERLAY_DARKEN
2603 }
2604 
initAllPages()2605 static void initAllPages()
2606 {
2607     Hu_MenuInitColorWidgetPage();
2608     Hu_MenuInitMainPage();
2609     //Hu_MenuInitGameTypePage();
2610     Hu_MenuInitEpisodePage();
2611 #if __JHEXEN__
2612     Hu_MenuInitPlayerClassPage();
2613 #endif
2614     Hu_MenuInitSkillPage();
2615     //Hu_MenuInitMultiplayerPage();
2616 #if __JHERETIC__ || __JHEXEN__
2617     Hu_MenuInitFilesPage();
2618 #endif
2619     Hu_MenuInitLoadGameAndSaveGamePages();
2620     Hu_MenuInitOptionsPage();
2621     Hu_MenuInitPlayerSetupPage();
2622     Hu_MenuInitGameplayOptionsPage();
2623     Hu_MenuInitSaveOptionsPage();
2624     Hu_MenuInitHUDOptionsPage();
2625     Hu_MenuInitAutomapOptionsPage();
2626     Hu_MenuInitWeaponsPage();
2627 #if __JHERETIC__ || __JHEXEN__
2628     Hu_MenuInitInventoryOptionsPage();
2629 #endif
2630     Hu_MenuInitSoundOptionsPage();
2631     Hu_MenuInitControlsPage();
2632 }
2633 
destroyAllPages()2634 static void destroyAllPages()
2635 {
2636     qDeleteAll(pages);
2637     pages.clear();
2638 }
2639 
Hu_MenuColorWidgetCmdResponder(Page & page,menucommand_e cmd)2640 int Hu_MenuColorWidgetCmdResponder(Page &page, menucommand_e cmd)
2641 {
2642     switch(cmd)
2643     {
2644     case MCMD_NAV_OUT: {
2645         Widget *wi = static_cast<Widget *>(page.userValue().value<void *>());
2646         wi->setFlags(Widget::Active, UnsetFlags);
2647         S_LocalSound(SFX_MENU_CANCEL, NULL);
2648         colorWidgetActive = false;
2649 
2650         /// @kludge We should re-focus on the object instead.
2651         cursor.angle = 0; // Stop cursor rotation animation dead (don't rewind).
2652         Hu_MenuUpdateCursorState();
2653         /// kludge end.
2654         return true; }
2655 
2656     case MCMD_NAV_PAGEUP:
2657     case MCMD_NAV_PAGEDOWN:
2658         return true; // Eat these.
2659 
2660     case MCMD_SELECT: {
2661         Widget *wi = static_cast<Widget *>(page.userValue().value<void *>());
2662         ColorEditWidget &cbox = wi->as<ColorEditWidget>();
2663         cbox.setFlags(Widget::Active, UnsetFlags);
2664         S_LocalSound(SFX_MENU_ACCEPT, NULL);
2665         colorWidgetActive = false;
2666         cbox.setColor(page.findWidget(Widget::Id0).as<ColorEditWidget>().color(), 0);
2667 
2668         /// @kludge We should re-focus on the object instead.
2669         cursor.angle = 0; // Stop cursor rotation animation dead (don't rewind).
2670         Hu_MenuUpdateCursorState();
2671         /// kludge end.
2672         return true; }
2673 
2674     default: break;
2675     }
2676 
2677     return false;
2678 }
2679 
2680 /**
2681  * Determines if manual episode selection via the menu can be skipped if only one
2682  * episode is playable.
2683  *
2684  * Some demo/shareware game versions use the episode selection menu for the purpose
2685  * of prompting the user to buy the full version. In such a case, disable skipping.
2686  *
2687  * @return  @c true if skipping is allowed.
2688  */
allowSkipEpisodeSelection()2689 static bool allowSkipEpisodeSelection()
2690 {
2691 #if __JDOOM__
2692     if(gameMode == doom_shareware)    return false; // Never.
2693 #elif __JHERETIC__
2694     if(gameMode == heretic_shareware) return false; // Never.
2695 #endif
2696     return true;
2697 }
2698 
Hu_MenuSkipPreviousPageIfSkippingEpisodeSelection(Page & page,menucommand_e cmd)2699 int Hu_MenuSkipPreviousPageIfSkippingEpisodeSelection(Page &page, menucommand_e cmd)
2700 {
2701     // All we react to are MCMD_NAV_OUT commands.
2702     if(cmd != MCMD_NAV_OUT) return false;
2703 
2704     Page *previous = page.previousPage();
2705 
2706     // Skip this page if only one episode is playable.
2707     if(allowSkipEpisodeSelection() && PlayableEpisodeCount() == 1)
2708     {
2709         previous = previous->previousPage();
2710     }
2711 
2712     if(previous)
2713     {
2714         S_LocalSound(SFX_MENU_CANCEL, nullptr);
2715         Hu_MenuSetPage(previous);
2716     }
2717     else
2718     {
2719         // No previous page so just close the menu.
2720         S_LocalSound(SFX_MENU_CLOSE, nullptr);
2721         Hu_MenuCommand(MCMD_CLOSE);
2722     }
2723 
2724     return true;
2725 }
2726 
2727 /// Depending on the current menu state some commands require translating.
translateCommand(menucommand_e cmd)2728 static menucommand_e translateCommand(menucommand_e cmd)
2729 {
2730     // If a close command is received while currently working with a selected
2731     // "active" widget - interpret the command instead as "navigate out".
2732     if(menuActive && (cmd == MCMD_CLOSE || cmd == MCMD_CLOSEFAST))
2733     {
2734         if(Widget *wi = Hu_MenuPage().focusWidget())
2735         {
2736             if(wi->isActive() &&
2737                (is<LineEditWidget>(wi) || is<ListWidget>(wi) || is<ColorEditWidget>(wi)))
2738             {
2739                 cmd = MCMD_NAV_OUT;
2740             }
2741         }
2742     }
2743 
2744     return cmd;
2745 }
2746 
Hu_MenuCommand(menucommand_e cmd)2747 void Hu_MenuCommand(menucommand_e cmd)
2748 {
2749     cmd = translateCommand(cmd);
2750 
2751     // Determine the page which will respond to this command.
2752     Page *page = colorWidgetActive? Hu_MenuPagePtr("ColorWidget") : Hu_MenuPagePtr();
2753 
2754     if(cmd == MCMD_CLOSE || cmd == MCMD_CLOSEFAST)
2755     {
2756         if(menuActive)
2757         {
2758             //BusyMode_FreezeGameForBusyMode();
2759 
2760             menuNominatingQuickSaveSlot = false;
2761 
2762             Hu_FogEffectSetAlphaTarget(0);
2763 
2764             if(cmd == MCMD_CLOSEFAST)
2765             {
2766                 // Hide the menu instantly.
2767                 mnAlpha = mnTargetAlpha = 0;
2768             }
2769             else
2770             {
2771                 mnTargetAlpha = 0;
2772             }
2773 
2774             if(cmd != MCMD_CLOSEFAST)
2775             {
2776                 S_LocalSound(SFX_MENU_CLOSE, NULL);
2777             }
2778 
2779             menuActive = false;
2780 
2781             // Disable the menu binding context.
2782             DD_Execute(true, "deactivatebcontext menu");
2783         }
2784         return;
2785     }
2786 
2787     // No other commands are responded to once shutdown has begun.
2788     if(G_QuitInProgress())
2789     {
2790         return;
2791     }
2792 
2793     if(!menuActive)
2794     {
2795         if(MCMD_OPEN == cmd)
2796         {
2797             // If anyone is currently chatting; the menu cannot be opened.
2798             for(int i = 0; i < MAXPLAYERS; ++i)
2799             {
2800                 if(ST_ChatIsActive(i))
2801                     return;
2802             }
2803 
2804             S_LocalSound(SFX_MENU_OPEN, NULL);
2805 
2806             //Con_Open(false);
2807 
2808             Hu_FogEffectSetAlphaTarget(1);
2809             Hu_MenuSetOpacity(1);
2810             menuActive = true;
2811             menuTime = 0;
2812 
2813             currentPage = NULL; // Always re-activate this page.
2814             Hu_MenuSetPage("Main");
2815 
2816             // Enable the menu binding class
2817             DD_Execute(true, "activatebcontext menu");
2818             B_SetContextFallback("menu", Hu_MenuFallbackResponder);
2819         }
2820         return;
2821     }
2822 
2823     page->handleCommand(cmd);
2824 }
2825 
Hu_MenuPrivilegedResponder(event_t * ev)2826 int Hu_MenuPrivilegedResponder(event_t *ev)
2827 {
2828     DENG2_ASSERT(ev);
2829     if(Hu_MenuIsActive())
2830     {
2831         if(Widget *focused = Hu_MenuPage().focusWidget())
2832         {
2833             if(!focused->isDisabled())
2834             {
2835                 return focused->handleEvent_Privileged(*ev);
2836             }
2837         }
2838     }
2839     return false;
2840 }
2841 
Hu_MenuResponder(event_t * ev)2842 int Hu_MenuResponder(event_t *ev)
2843 {
2844     DENG2_ASSERT(ev);
2845     if(Hu_MenuIsActive())
2846     {
2847         if(Widget *focused = Hu_MenuPage().focusWidget())
2848         {
2849             if(!focused->isDisabled())
2850             {
2851                 return focused->handleEvent(*ev);
2852             }
2853         }
2854     }
2855     return false; // Not eaten.
2856 }
2857 
Hu_MenuFallbackResponder(event_t * ev)2858 int Hu_MenuFallbackResponder(event_t *ev)
2859 {
2860     DENG2_ASSERT(ev);
2861     Page *page = Hu_MenuPagePtr();
2862 
2863     if(!Hu_MenuIsActive() || !page) return false;
2864 
2865     if(cfg.common.menuShortcutsEnabled)
2866     {
2867         if(ev->type == EV_KEY && (ev->state == EVS_DOWN || ev->state == EVS_REPEAT))
2868         {
2869             for(Widget *wi : page->children())
2870             {
2871                 if(wi->isDisabled() || wi->isHidden())
2872                     continue;
2873 
2874                 if(wi->flags() & Widget::NoFocus)
2875                     continue;
2876 
2877                 if(wi->shortcut() == ev->data1)
2878                 {
2879                     page->setFocus(wi);
2880                     return true;
2881                 }
2882             }
2883         }
2884     }
2885     return false;
2886 }
2887 
2888 /**
2889  * User wants to load this game
2890  */
Hu_MenuSelectLoadSlot(Widget & wi,Widget::Action action)2891 void Hu_MenuSelectLoadSlot(Widget &wi, Widget::Action action)
2892 {
2893     LineEditWidget *edit = &wi.as<LineEditWidget>();
2894 
2895     if(action != Widget::Deactivated) return;
2896 
2897     // Linked focus between LoadGame and SaveGame pages.
2898     Page &saveGamePage = Hu_MenuPage("SaveGame");
2899     saveGamePage.setFocus(saveGamePage.tryFindWidget(wi.userValue2().toUInt()));
2900 
2901     Page &loadGamePage = Hu_MenuPage("LoadGame");
2902     loadGamePage.setFocus(loadGamePage.tryFindWidget(wi.userValue2().toUInt()));
2903 
2904     G_SetGameActionLoadSession(edit->userValue().toString());
2905     Hu_MenuCommand(chooseCloseMethod());
2906 }
2907 
2908 #if __JHERETIC__ || __JHEXEN__
Hu_MenuDrawMainPage(Page const &,Vector2i const & origin)2909 void Hu_MenuDrawMainPage(Page const & /*page*/, Vector2i const &origin)
2910 {
2911 #define TITLEOFFSET_X         (-22)
2912 #define TITLEOFFSET_Y         (-56)
2913 
2914 #if __JHEXEN__
2915     int frame = (menuTime / 5) % 7;
2916 #endif
2917 
2918     DGL_Enable(DGL_TEXTURE_2D);
2919     DGL_Color4f(1, 1, 1, mnRendState->pageAlpha);
2920     FR_SetFont(FID(GF_FONTB));
2921     FR_SetColorAndAlpha(1, 1, 1, mnRendState->pageAlpha);
2922 
2923     WI_DrawPatch(pMainTitle, Hu_ChoosePatchReplacement(patchreplacemode_t(cfg.common.menuPatchReplaceMode), pMainTitle),
2924                  Vector2i(origin.x + TITLEOFFSET_X, origin.y + TITLEOFFSET_Y), ALIGN_TOPLEFT, 0, Hu_MenuMergeEffectWithDrawTextFlags(0));
2925 #if __JHEXEN__
2926     GL_DrawPatch(pBullWithFire[(frame + 2) % 7], origin + Vector2i(-73, 24));
2927     GL_DrawPatch(pBullWithFire[frame],           origin + Vector2i(168, 24));
2928 #elif __JHERETIC__
2929     GL_DrawPatch(pRotatingSkull[17 - frame],     origin + Vector2i(-70, -46));
2930     GL_DrawPatch(pRotatingSkull[frame],          origin + Vector2i(122, -46));
2931 #endif
2932 
2933     DGL_Disable(DGL_TEXTURE_2D);
2934 
2935 #undef TITLEOFFSET_Y
2936 #undef TITLEOFFSET_X
2937 }
2938 #endif
2939 
Hu_MenuDrawGameTypePage(Page const &,Vector2i const & origin)2940 void Hu_MenuDrawGameTypePage(Page const & /*page*/, Vector2i const &origin)
2941 {
2942     Hu_MenuDrawPageTitle(GET_TXT(TXT_PICKGAMETYPE), Vector2i(SCREENWIDTH / 2, origin.y - 28));
2943 }
2944 
2945 #if __JHEXEN__
2946 /**
2947  * A specialization of MNRect_Ticker() which implements the animation logic
2948  * for the player class selection page's player visual background.
2949  */
Hu_MenuPlayerClassBackgroundTicker(Widget & wi)2950 void Hu_MenuPlayerClassBackgroundTicker(Widget &wi)
2951 {
2952     RectWidget &bg = wi.as<RectWidget>();
2953 
2954     // Determine our selection according to the current focus object.
2955     /// @todo Do not search for the focus object, flag the "random"
2956     ///        state through a focus action.
2957     if(Widget *mop = wi.page().focusWidget())
2958     {
2959         playerclass_t pClass = playerclass_t(mop->userValue2().toInt());
2960         if(pClass == PCLASS_NONE)
2961         {
2962             // Random class.
2963             /// @todo Use this object's timer instead of menuTime.
2964             pClass = playerclass_t(menuTime / 5);
2965         }
2966 
2967         /// @todo Only change here if in the "random" state.
2968         pClass = playerclass_t(int(pClass) % 3); // Number of user-selectable classes.
2969 
2970         bg.setBackgroundPatch(pPlayerClassBG[pClass]);
2971     }
2972 }
2973 
2974 /**
2975  * A specialization of MNMobjPreview_Ticker() which implements the animation
2976  * logic for the player class selection page's player visual.
2977  */
Hu_MenuPlayerClassPreviewTicker(Widget & wi)2978 void Hu_MenuPlayerClassPreviewTicker(Widget &wi)
2979 {
2980     MobjPreviewWidget &mprev = wi.as<MobjPreviewWidget>();
2981 
2982     // Determine our selection according to the current focus object.
2983     /// @todo Do not search for the focus object, flag the "random"
2984     ///        state through a focus action.
2985     if(Widget *mop = wi.page().focusWidget())
2986     {
2987         playerclass_t pClass = playerclass_t(mop->userValue2().toInt());
2988         if(pClass == PCLASS_NONE)
2989         {
2990             // Random class.
2991             /// @todo Use this object's timer instead of menuTime.
2992             pClass = playerclass_t(PCLASS_FIRST + (menuTime / 5));
2993             pClass = playerclass_t(int(pClass) % 3); // Number of user-selectable classes.
2994 
2995             mprev.setPlayerClass(pClass);
2996             mprev.setMobjType(PCLASS_INFO(pClass)->mobjType);
2997         }
2998 
2999         // Fighter is Yellow, others Red by default.
3000         mprev.setTranslationClass(pClass);
3001         mprev.setTranslationMap(pClass == PCLASS_FIGHTER? 2 : 0);
3002     }
3003 }
3004 
Hu_MenuDrawPlayerClassPage(Page const &,Vector2i const & origin)3005 void Hu_MenuDrawPlayerClassPage(Page const & /*page*/, Vector2i const &origin)
3006 {
3007     DGL_Enable(DGL_TEXTURE_2D);
3008     FR_SetFont(FID(GF_FONTB));
3009     FR_SetColorAndAlpha(cfg.common.menuTextColors[0][CR], cfg.common.menuTextColors[0][CG], cfg.common.menuTextColors[0][CB], mnRendState->pageAlpha);
3010 
3011     FR_DrawTextXY3("Choose class:", origin.x - 32, origin.y - 42, ALIGN_TOPLEFT,
3012                    Hu_MenuMergeEffectWithDrawTextFlags(0));
3013 
3014     DGL_Disable(DGL_TEXTURE_2D);
3015 }
3016 #endif
3017 
Hu_MenuDrawEpisodePage(Page const & page,Vector2i const & origin)3018 void Hu_MenuDrawEpisodePage(Page const &page, Vector2i const &origin)
3019 {
3020 #if __JDOOM__
3021     DENG2_UNUSED(page);
3022 
3023     DGL_Enable(DGL_TEXTURE_2D);
3024     DGL_Color4f(1, 1, 1, mnRendState->pageAlpha);
3025 
3026     FR_SetFont(FID(GF_FONTB));
3027     FR_SetColorv(cfg.common.menuTextColors[0]);
3028     FR_SetAlpha(mnRendState->pageAlpha);
3029 
3030     WI_DrawPatch(pEpisode, Hu_ChoosePatchReplacement(patchreplacemode_t(cfg.common.menuPatchReplaceMode), pEpisode),
3031                  Vector2i(origin.x + 7, origin.y - 25), ALIGN_TOPLEFT, 0, Hu_MenuMergeEffectWithDrawTextFlags(0));
3032 
3033     DGL_Disable(DGL_TEXTURE_2D);
3034 #else
3035     DENG2_UNUSED(page);
3036 
3037 #if defined (__JHERETIC__)
3038     String titleText;
3039 #else
3040     String titleText = "Choose episode:";
3041 #endif
3042 
3043     if (const auto *value = Defs().getValueById("Menu Label|Episode Page Title"))
3044     {
3045         titleText = value->text;
3046     }
3047 
3048     DGL_Enable(DGL_TEXTURE_2D);
3049     FR_SetFont(FID(GF_FONTB));
3050     FR_SetColorAndAlpha(cfg.common.menuTextColors[0][CR], cfg.common.menuTextColors[0][CG], cfg.common.menuTextColors[0][CB], mnRendState->pageAlpha);
3051 
3052     FR_DrawTextXY3(titleText.toLatin1(), SCREENWIDTH / 2, origin.y - 42, ALIGN_TOP,
3053                    Hu_MenuMergeEffectWithDrawTextFlags(0));
3054 
3055     DGL_Disable(DGL_TEXTURE_2D);
3056 #endif
3057 }
3058 
Hu_MenuDrawSkillPage(Page const &,Vector2i const & origin)3059 void Hu_MenuDrawSkillPage(Page const & /*page*/, Vector2i const &origin)
3060 {
3061 #if __JDOOM__ || __JDOOM64__
3062     DGL_Enable(DGL_TEXTURE_2D);
3063     DGL_Color4f(1, 1, 1, mnRendState->pageAlpha);
3064     FR_SetFont(FID(GF_FONTB));
3065     FR_SetColorAndAlpha(cfg.common.menuTextColors[0][CR], cfg.common.menuTextColors[0][CG], cfg.common.menuTextColors[0][CB], mnRendState->pageAlpha);
3066 
3067     WI_DrawPatch(pNewGame, Hu_ChoosePatchReplacement(patchreplacemode_t(cfg.common.menuPatchReplaceMode), pNewGame),
3068                  Vector2i(origin.x + 48, origin.y - 49), ALIGN_TOPLEFT, 0, Hu_MenuMergeEffectWithDrawTextFlags(0));
3069     WI_DrawPatch(pSkill, Hu_ChoosePatchReplacement(patchreplacemode_t(cfg.common.menuPatchReplaceMode), pSkill),
3070                  Vector2i(origin.x + 6,  origin.y - 25), ALIGN_TOPLEFT, 0, Hu_MenuMergeEffectWithDrawTextFlags(0));
3071 
3072     DGL_Disable(DGL_TEXTURE_2D);
3073 #else
3074 #if defined (__JHERETIC__)
3075     String titleText;
3076 #else
3077     String titleText = "Choose Skill Level:";
3078 #endif
3079 
3080     if (const auto *value = Defs().getValueById("Menu Label|Skill Page Title"))
3081     {
3082         titleText = value->text;
3083     }
3084 
3085     Hu_MenuDrawPageTitle(titleText, Vector2i(SCREENWIDTH / 2, origin.y - 28));
3086 #endif
3087 }
3088 
3089 /**
3090  * Called after the save name has been modified and to action the game-save.
3091  */
Hu_MenuSelectSaveSlot(Widget & wi,Widget::Action action)3092 void Hu_MenuSelectSaveSlot(Widget &wi, Widget::Action action)
3093 {
3094     if(action != Widget::Deactivated) return;
3095 
3096     LineEditWidget &edit = wi.as<LineEditWidget>();
3097     String const saveSlotId = edit.userValue().toString();
3098 
3099     if(menuNominatingQuickSaveSlot)
3100     {
3101         Con_SetInteger("game-save-quick-slot", saveSlotId.toInt());
3102         menuNominatingQuickSaveSlot = false;
3103     }
3104 
3105     String userDescription = edit.text();
3106     if(!G_SetGameActionSaveSession(saveSlotId, &userDescription))
3107     {
3108         return;
3109     }
3110 
3111     Page &saveGamePage = Hu_MenuPage("SaveGame");
3112     saveGamePage.setFocus(saveGamePage.tryFindWidget(wi.userValue2().toUInt()));
3113 
3114     Page &loadGamePage = Hu_MenuPage("LoadGame");
3115     loadGamePage.setFocus(loadGamePage.tryFindWidget(wi.userValue2().toUInt()));
3116 
3117     Hu_MenuCommand(chooseCloseMethod());
3118 }
3119 
Hu_MenuSaveSlotEdit(Widget & wi,Widget::Action action)3120 void Hu_MenuSaveSlotEdit(Widget &wi, Widget::Action action)
3121 {
3122     if(action != Widget::Activated) return;
3123     if(cfg.common.menuGameSaveSuggestDescription)
3124     {
3125         auto &edit = wi.as<LineEditWidget>();
3126         edit.setText(G_DefaultGameStateFolderUserDescription("" /*don't reuse an existing description*/));
3127     }
3128 }
3129 
Hu_MenuActivateColorWidget(Widget & wi,Widget::Action action)3130 void Hu_MenuActivateColorWidget(Widget &wi, Widget::Action action)
3131 {
3132     if(action != Widget::Activated) return;
3133 
3134     ColorEditWidget &cbox = wi.as<ColorEditWidget>();
3135 
3136     Page &colorWidgetPage    = Hu_MenuPage("ColorWidget");
3137     ColorEditWidget &cboxMix = colorWidgetPage.findWidget(Widget::Id0).as<ColorEditWidget>();
3138     SliderWidget &sldrRed    = colorWidgetPage.findWidget(Widget::Id1).as<SliderWidget>();
3139     SliderWidget &sldrGreen  = colorWidgetPage.findWidget(Widget::Id2).as<SliderWidget>();
3140     SliderWidget &sldrBlue   = colorWidgetPage.findWidget(Widget::Id3).as<SliderWidget>();
3141     LabelWidget  &labelAlpha = colorWidgetPage.findWidget(Widget::Id4).as<LabelWidget>();
3142     SliderWidget &sldrAlpha  = colorWidgetPage.findWidget(Widget::Id5).as<SliderWidget>();
3143 
3144     colorWidgetActive = true;
3145 
3146     colorWidgetPage.activate();
3147     colorWidgetPage.setUserValue(qVariantFromValue((void *)&wi)); // Ugly or what...
3148 
3149     cboxMix.setColor(cbox.color(), 0);
3150 
3151     sldrRed  .setValue(cbox.red());
3152     sldrGreen.setValue(cbox.green());
3153     sldrBlue .setValue(cbox.blue());
3154     sldrAlpha.setValue(cbox.alpha());
3155 
3156     labelAlpha.setFlags(Widget::Disabled | Widget::Hidden, (cbox.rgbaMode()? UnsetFlags : SetFlags));
3157     sldrAlpha. setFlags(Widget::Disabled | Widget::Hidden, (cbox.rgbaMode()? UnsetFlags : SetFlags));
3158 }
3159 
Hu_MenuDrawLoadGamePage(Page const &,Vector2i const & origin)3160 void Hu_MenuDrawLoadGamePage(Page const & /*page*/, Vector2i const &origin)
3161 {
3162     DGL_Enable(DGL_TEXTURE_2D);
3163     DGL_Color4f(1, 1, 1, mnRendState->pageAlpha);
3164     FR_SetFont(FID(GF_FONTB));
3165     FR_SetColorAndAlpha(cfg.common.menuTextColors[0][CR], cfg.common.menuTextColors[0][CG], cfg.common.menuTextColors[0][CB], mnRendState->pageAlpha);
3166 
3167 #if __JHERETIC__ || __JHEXEN__
3168     FR_DrawTextXY3(Widget::labelText("Load Game").toLatin1(), SCREENWIDTH / 2, origin.y - 20, ALIGN_TOP, Hu_MenuMergeEffectWithDrawTextFlags(0));
3169 #else
3170     WI_DrawPatch(pLoadGame, Hu_ChoosePatchReplacement(patchreplacemode_t(cfg.common.menuPatchReplaceMode), pLoadGame),
3171                  Vector2i(origin.x - 8, origin.y - 26), ALIGN_TOPLEFT, 0, Hu_MenuMergeEffectWithDrawTextFlags(0));
3172 #endif
3173     DGL_Disable(DGL_TEXTURE_2D);
3174 
3175     Vector2i helpOrigin(SCREENWIDTH / 2, (SCREENHEIGHT / 2) + ((SCREENHEIGHT / 2 - 5) / cfg.common.menuScale));
3176     Hu_MenuDrawPageHelp("Select to load, [Del] to clear", helpOrigin);
3177 }
3178 
Hu_MenuDrawSaveGamePage(Page const &,Vector2i const & origin)3179 void Hu_MenuDrawSaveGamePage(Page const & /*page*/, Vector2i const &origin)
3180 {
3181 #if __JHERETIC__ || __JHEXEN__
3182     Hu_MenuDrawPageTitle("Save Game", Vector2i(SCREENWIDTH / 2, origin.y - 20));
3183 #else
3184     DGL_Enable(DGL_TEXTURE_2D);
3185     DGL_Color4f(1, 1, 1, mnRendState->pageAlpha);
3186     FR_SetFont(FID(GF_FONTB));
3187     FR_SetColorAndAlpha(cfg.common.menuTextColors[0][CR], cfg.common.menuTextColors[0][CG], cfg.common.menuTextColors[0][CB], mnRendState->pageAlpha);
3188 
3189     WI_DrawPatch(pSaveGame, Hu_ChoosePatchReplacement(patchreplacemode_t(cfg.common.menuPatchReplaceMode), pSaveGame),
3190                  Vector2i(origin.x - 8, origin.y - 26), ALIGN_TOPLEFT, 0, Hu_MenuMergeEffectWithDrawTextFlags(0));
3191 
3192     DGL_Disable(DGL_TEXTURE_2D);
3193 #endif
3194 
3195     Vector2i helpOrigin(SCREENWIDTH / 2, (SCREENHEIGHT / 2) + ((SCREENHEIGHT / 2 - 5) / cfg.common.menuScale));
3196     Hu_MenuDrawPageHelp("Select to save, [Del] to clear", helpOrigin);
3197 }
3198 
3199 #if __JDOOM__ || __JHERETIC__ || __JHEXEN__
Hu_MenuSelectHelp(Widget &,Widget::Action action)3200 void Hu_MenuSelectHelp(Widget & /*wi*/, Widget::Action action)
3201 {
3202     if(action != Widget::Deactivated) return;
3203     G_StartHelp();
3204 }
3205 #endif
3206 
Hu_MenuDrawOptionsPage(Page const &,Vector2i const & origin)3207 void Hu_MenuDrawOptionsPage(Page const & /*page*/, Vector2i const &origin)
3208 {
3209 #if __JHERETIC__ || __JHEXEN__
3210     Hu_MenuDrawPageTitle("Options", Vector2i(origin.x + 42, origin.y - 30));
3211 #else
3212     DGL_Enable(DGL_TEXTURE_2D);
3213     DGL_Color4f(1, 1, 1, mnRendState->pageAlpha);
3214     FR_SetFont(FID(GF_FONTB));
3215     FR_SetColorAndAlpha(cfg.common.menuTextColors[0][CR], cfg.common.menuTextColors[0][CG], cfg.common.menuTextColors[0][CB], mnRendState->pageAlpha);
3216 
3217     WI_DrawPatch(pOptionsTitle, Hu_ChoosePatchReplacement(patchreplacemode_t(cfg.common.menuPatchReplaceMode), pOptionsTitle),
3218                  Vector2i(origin.x + 42, origin.y - 20), ALIGN_TOP, 0, Hu_MenuMergeEffectWithDrawTextFlags(0));
3219 
3220     DGL_Disable(DGL_TEXTURE_2D);
3221 #endif
3222 }
3223 
Hu_MenuDrawMultiplayerPage(Page const &,Vector2i const & origin)3224 void Hu_MenuDrawMultiplayerPage(Page const & /*page*/, Vector2i const &origin)
3225 {
3226     Hu_MenuDrawPageTitle(GET_TXT(TXT_MULTIPLAYER), Vector2i(SCREENWIDTH / 2, origin.y - 28));
3227 }
3228 
Hu_MenuDrawPlayerSetupPage(Page const &,Vector2i const & origin)3229 void Hu_MenuDrawPlayerSetupPage(Page const & /*page*/, Vector2i const &origin)
3230 {
3231     Hu_MenuDrawPageTitle(GET_TXT(TXT_PLAYERSETUP), Vector2i(SCREENWIDTH / 2, origin.y - 28));
3232 }
3233 
Hu_MenuActionSetActivePage(Widget & wi,Widget::Action action)3234 void Hu_MenuActionSetActivePage(Widget &wi, Widget::Action action)
3235 {
3236     if(action != Widget::Deactivated) return;
3237     Hu_MenuSetPage(Hu_MenuPagePtr(wi.as<ButtonWidget>().userValue().toString()));
3238 }
3239 
Hu_MenuUpdateColorWidgetColor(Widget & wi,Widget::Action action)3240 void Hu_MenuUpdateColorWidgetColor(Widget &wi, Widget::Action action)
3241 {
3242     if(action != Widget::Modified) return;
3243 
3244     SliderWidget &sldr = wi.as<SliderWidget>();
3245     float value = sldr.value();
3246     ColorEditWidget &cboxMix = Hu_MenuPage("ColorWidget").findWidget(Widget::Id0).as<ColorEditWidget>();
3247 
3248     int const component = wi.userValue2().toInt();
3249     switch(component)
3250     {
3251     case CR: cboxMix.setRed  (value); break;
3252     case CG: cboxMix.setGreen(value); break;
3253     case CB: cboxMix.setBlue (value); break;
3254     case CA: cboxMix.setAlpha(value); break;
3255 
3256     default: DENG2_ASSERT(!"Hu_MenuUpdateColorWidgetColor: Invalid value for data2.");
3257     }
3258 }
3259 
Hu_MenuChangeWeaponPriority(Widget & wi,Widget::Action action)3260 void Hu_MenuChangeWeaponPriority(Widget &wi, Widget::Action action)
3261 {
3262     if (action == Widget::Modified)
3263     {
3264         auto &list = wi.as<ListWidget>();
3265         for (int i = 0; i < list.itemCount(); ++i)
3266         {
3267             cfg.common.weaponOrder[i] = list.itemData(i);
3268         }
3269     }
3270 }
3271 
Hu_MenuSelectSingleplayer(Widget &,Widget::Action action)3272 void Hu_MenuSelectSingleplayer(Widget & /*wi*/, Widget::Action action)
3273 {
3274     if(action != Widget::Deactivated) return;
3275 
3276     // If a networked game is already in progress inform the user we can't continue.
3277     /// @todo Allow continue: Ask the user if the networked game should be stopped.
3278     if(IS_NETGAME)
3279     {
3280         Hu_MsgStart(MSG_ANYKEY, NEWGAME, nullptr, 0, nullptr);
3281         return;
3282     }
3283 
3284     // Skip episode selection if only one is playable.
3285     if(allowSkipEpisodeSelection() && PlayableEpisodeCount() == 1)
3286     {
3287         mnEpisode = FirstPlayableEpisodeId();
3288 #if __JHEXEN__
3289         Hu_MenuSetPage("PlayerClass");
3290 #else
3291         Hu_MenuSetPage("Skill");
3292 #endif
3293         return;
3294     }
3295 
3296     // Show the episode selection menu.
3297     Hu_MenuSetPage("Episode");
3298 }
3299 
3300 #if 0
3301 void Hu_MenuSelectMultiplayer(Widget & /*wi*/, Widget::Action action)
3302 {
3303     if(action != Widget::Deactivated) return;
3304 
3305     Page &multiplayerPage = Hu_MenuPage("Multiplayer");
3306 
3307     // Set the appropriate label.
3308     ButtonWidget *btn = &multiplayerPage.findWidget(Widget::Id0).as<ButtonWidget>();
3309     if(IS_NETGAME)
3310     {
3311         btn->setText("Disconnect");
3312     }
3313     else
3314     {
3315         btn->setText("Join Game");
3316     }
3317 
3318     Hu_MenuSetPage(&multiplayerPage);
3319 }
3320 #endif
3321 
Hu_MenuSelectJoinGame(Widget &,Widget::Action action)3322 void Hu_MenuSelectJoinGame(Widget & /*wi*/, Widget::Action action)
3323 {
3324     if(action != Widget::Deactivated) return;
3325 
3326     if(IS_NETGAME)
3327     {
3328         DD_Execute(false, "net disconnect");
3329         Hu_MenuCommand(MCMD_CLOSE);
3330         return;
3331     }
3332 
3333     DD_Execute(false, "net setup client");
3334 }
3335 
Hu_MenuActivatePlayerSetup(Page & page)3336 void Hu_MenuActivatePlayerSetup(Page &page)
3337 {
3338     MobjPreviewWidget &mop = page.findWidget(Widget::Id0).as<MobjPreviewWidget>();
3339     LineEditWidget &name   = page.findWidget(Widget::Id1).as<LineEditWidget>();
3340     ListWidget &color      = page.findWidget(Widget::Id3).as<ListWidget>();
3341 
3342 #if __JHEXEN__
3343     mop.setMobjType(PCLASS_INFO(cfg.netClass)->mobjType);
3344     mop.setPlayerClass(cfg.netClass);
3345 #else
3346     mop.setMobjType(MT_PLAYER);
3347     mop.setPlayerClass(PCLASS_PLAYER);
3348 #endif
3349     mop.setTranslationClass(0);
3350     mop.setTranslationMap(cfg.common.netColor);
3351 
3352     color.selectItemByValue(cfg.common.netColor);
3353 #if __JHEXEN__
3354     ListWidget &class_ = page.findWidget(Widget::Id2).as<ListWidget>();
3355     class_.selectItemByValue(cfg.netClass);
3356 #endif
3357 
3358     name.setText(Con_GetString("net-name"), MNEDIT_STF_NO_ACTION | MNEDIT_STF_REPLACEOLD);
3359 }
3360 
3361 #if __JHEXEN__
Hu_MenuSelectPlayerSetupPlayerClass(Widget & wi,Widget::Action action)3362 void Hu_MenuSelectPlayerSetupPlayerClass(Widget &wi, Widget::Action action)
3363 {
3364     if(action != Widget::Modified) return;
3365 
3366     ListWidget &list = wi.as<ListWidget>();
3367     int selection = list.selection();
3368     if(selection >= 0)
3369     {
3370         MobjPreviewWidget &mop = wi.page().findWidget(Widget::Id0).as<MobjPreviewWidget>();
3371         mop.setPlayerClass(selection);
3372         mop.setMobjType(PCLASS_INFO(selection)->mobjType);
3373     }
3374 }
3375 #endif
3376 
Hu_MenuSelectPlayerColor(Widget & wi,Widget::Action action)3377 void Hu_MenuSelectPlayerColor(Widget &wi, Widget::Action action)
3378 {
3379     if(action != Widget::Modified) return;
3380 
3381     // The color translation map is stored in the list item data member.
3382     ListWidget &list = wi.as<ListWidget>();
3383     int selection = list.itemData(list.selection());
3384     if(selection >= 0)
3385     {
3386         wi.page().findWidget(Widget::Id0).as<MobjPreviewWidget>().setTranslationMap(selection);
3387     }
3388 }
3389 
Hu_MenuSelectAcceptPlayerSetup(Widget & wi,Widget::Action action)3390 void Hu_MenuSelectAcceptPlayerSetup(Widget &wi, Widget::Action action)
3391 {
3392     Page &page                  = wi.page();
3393     LineEditWidget &plrNameEdit = page.findWidget(Widget::Id1).as<LineEditWidget>();
3394 #if __JHEXEN__
3395     ListWidget &plrClassList    = page.findWidget(Widget::Id2).as<ListWidget>();
3396 #endif
3397     ListWidget &plrColorList    = page.findWidget(Widget::Id3).as<ListWidget>();
3398 
3399 #if __JHEXEN__
3400     cfg.netClass = plrClassList.selection();
3401 #endif
3402     // The color translation map is stored in the list item data member.
3403     cfg.common.netColor = plrColorList.itemData(plrColorList.selection());
3404 
3405     if(action != Widget::Deactivated) return;
3406 
3407     char buf[300];
3408     strcpy(buf, "net-name ");
3409     M_StrCatQuoted(buf, plrNameEdit.text().toUtf8().constData(), 300);
3410     DD_Execute(false, buf);
3411 
3412     if(IS_NETGAME)
3413     {
3414         strcpy(buf, "setname ");
3415         M_StrCatQuoted(buf, plrNameEdit.text().toUtf8().constData(), 300);
3416         DD_Execute(false, buf);
3417 #if __JHEXEN__
3418         // Must do 'setclass' first; the real class and color do not change
3419         // until the server sends us a notification -- this means if we do
3420         // 'setcolor' first, the 'setclass' after it will override the color
3421         // change (or such would appear to be the case).
3422         DD_Executef(false, "setclass %i", cfg.netClass);
3423 #endif
3424         DD_Executef(false, "setcolor %i", cfg.common.netColor);
3425     }
3426 
3427     Hu_MenuSetPage("Options");
3428 }
3429 
Hu_MenuSelectQuitGame(Widget &,Widget::Action action)3430 void Hu_MenuSelectQuitGame(Widget & /*wi*/, Widget::Action action)
3431 {
3432     if(action != Widget::Deactivated) return;
3433     G_QuitGame();
3434 }
3435 
Hu_MenuSelectEndGame(Widget &,Widget::Action action)3436 void Hu_MenuSelectEndGame(Widget & /*wi*/, Widget::Action action)
3437 {
3438     if(action != Widget::Deactivated) return;
3439     DD_Executef(true, "endgame");
3440 }
3441 
Hu_MenuSelectLoadGame(Widget &,Widget::Action action)3442 void Hu_MenuSelectLoadGame(Widget & /*wi*/, Widget::Action action)
3443 {
3444     if(action != Widget::Deactivated) return;
3445 
3446     if(!Get(DD_NOVIDEO))
3447     {
3448         if(IS_CLIENT && !Get(DD_PLAYBACK))
3449         {
3450             Hu_MsgStart(MSG_ANYKEY, LOADNET, NULL, 0, NULL);
3451             return;
3452         }
3453     }
3454 
3455     Hu_MenuSetPage("LoadGame");
3456 }
3457 
Hu_MenuSelectSaveGame(Widget &,Widget::Action action)3458 void Hu_MenuSelectSaveGame(Widget & /*wi*/, Widget::Action action)
3459 {
3460     player_t *player = &players[CONSOLEPLAYER];
3461 
3462     if(action != Widget::Deactivated) return;
3463 
3464     if(!Get(DD_NOVIDEO))
3465     {
3466         if(IS_CLIENT)
3467         {
3468 #if __JDOOM__ || __JDOOM64__
3469             Hu_MsgStart(MSG_ANYKEY, SAVENET, NULL, 0, NULL);
3470 #endif
3471             return;
3472         }
3473 
3474         if(G_GameState() != GS_MAP)
3475         {
3476             Hu_MsgStart(MSG_ANYKEY, SAVEOUTMAP, NULL, 0, NULL);
3477             return;
3478         }
3479 
3480         if(player->playerState == PST_DEAD)
3481         {
3482             Hu_MsgStart(MSG_ANYKEY, SAVEDEAD, NULL, 0, NULL);
3483             return;
3484         }
3485     }
3486 
3487     Hu_MenuCommand(MCMD_OPEN);
3488     Hu_MenuSetPage("SaveGame");
3489 }
3490 
3491 #if __JHEXEN__
Hu_MenuSelectPlayerClass(Widget & wi,Widget::Action action)3492 void Hu_MenuSelectPlayerClass(Widget &wi, Widget::Action action)
3493 {
3494     Page &skillPage = Hu_MenuPage("Skill");
3495     int option = wi.userValue2().toInt();
3496 
3497     if(action != Widget::Deactivated) return;
3498 
3499     if(IS_NETGAME)
3500     {
3501         P_SetMessageWithFlags(&players[CONSOLEPLAYER], "You can't start a new game from within a netgame!", LMF_NO_HIDE);
3502         return;
3503     }
3504 
3505     if(option < 0)
3506     {
3507         // Random class.
3508         // Number of user-selectable classes.
3509         mnPlrClass = (menuTime / 5) % 3;
3510     }
3511     else
3512     {
3513         mnPlrClass = option;
3514     }
3515 
3516     ButtonWidget *btn;
3517     btn = &skillPage.findWidget(Widget::Id0).as<ButtonWidget>();
3518     btn->setText(GET_TXT(PCLASS_INFO(mnPlrClass)->skillModeName[SM_BABY]));
3519     if(!btn->text().isEmpty() && btn->text().first().isLetterOrNumber()) btn->setShortcut(btn->text().first().toLatin1());
3520 
3521     btn = &skillPage.findWidget(Widget::Id1).as<ButtonWidget>();
3522     btn->setText(GET_TXT(PCLASS_INFO(mnPlrClass)->skillModeName[SM_EASY]));
3523     if(!btn->text().isEmpty() && btn->text().first().isLetterOrNumber()) btn->setShortcut(btn->text().first().toLatin1());
3524 
3525     btn = &skillPage.findWidget(Widget::Id2).as<ButtonWidget>();
3526     btn->setText(GET_TXT(PCLASS_INFO(mnPlrClass)->skillModeName[SM_MEDIUM]));
3527     if(!btn->text().isEmpty() && btn->text().first().isLetterOrNumber()) btn->setShortcut(btn->text().first().toLatin1());
3528 
3529     btn = &skillPage.findWidget(Widget::Id3).as<ButtonWidget>();
3530     btn->setText(GET_TXT(PCLASS_INFO(mnPlrClass)->skillModeName[SM_HARD]));
3531     if(!btn->text().isEmpty() && btn->text().first().isLetterOrNumber()) btn->setShortcut(btn->text().first().toLatin1());
3532 
3533     btn = &skillPage.findWidget(Widget::Id4).as<ButtonWidget>();
3534     btn->setText(GET_TXT(PCLASS_INFO(mnPlrClass)->skillModeName[SM_NIGHTMARE]));
3535     if(!btn->text().isEmpty() && btn->text().first().isLetterOrNumber()) btn->setShortcut(btn->text().first().toLatin1());
3536 
3537     switch(mnPlrClass)
3538     {
3539     case PCLASS_FIGHTER:    skillPage.setX(120); break;
3540     case PCLASS_CLERIC:     skillPage.setX(116); break;
3541     case PCLASS_MAGE:       skillPage.setX(112); break;
3542     }
3543     Hu_MenuSetPage(&skillPage);
3544 }
3545 
Hu_MenuFocusOnPlayerClass(Widget & wi,Widget::Action action)3546 void Hu_MenuFocusOnPlayerClass(Widget &wi, Widget::Action action)
3547 {
3548     if(action != Widget::FocusGained) return;
3549 
3550     playerclass_t plrClass = playerclass_t(wi.userValue2().toInt());
3551     MobjPreviewWidget &mop = wi.page().findWidget(Widget::Id0).as<MobjPreviewWidget>();
3552     mop.setPlayerClass(plrClass);
3553     mop.setMobjType((PCLASS_NONE == plrClass? MT_NONE : PCLASS_INFO(plrClass)->mobjType));
3554 
3555     Hu_MenuDefaultFocusAction(wi, action);
3556 }
3557 #endif
3558 
Hu_MenuSelectEpisode(Widget & wi,Widget::Action)3559 void Hu_MenuSelectEpisode(Widget &wi, Widget::Action /*action*/)
3560 {
3561     mnEpisode = wi.as<ButtonWidget>().userValue().toString();
3562 #if __JHEXEN__
3563     Hu_MenuSetPage("PlayerClass");
3564 #else
3565     Hu_MenuSetPage("Skill");
3566 #endif
3567 }
3568 
3569 #if __JDOOM__ || __JHERETIC__
Hu_MenuConfirmOrderCommericalVersion(msgresponse_t,int,void *)3570 int Hu_MenuConfirmOrderCommericalVersion(msgresponse_t /*response*/, int /*userValue*/, void * /*context*/)
3571 {
3572     G_StartHelp();
3573     return true;
3574 }
3575 
Hu_MenuActivateNotSharewareEpisode(Widget &,Widget::Action action)3576 void Hu_MenuActivateNotSharewareEpisode(Widget & /*wi*/, Widget::Action action)
3577 {
3578     if(action != Widget::Deactivated) return;
3579     Hu_MsgStart(MSG_ANYKEY, SWSTRING, Hu_MenuConfirmOrderCommericalVersion, 0, NULL);
3580 }
3581 #endif
3582 
Hu_MenuFocusSkillMode(Widget & wi,Widget::Action action)3583 void Hu_MenuFocusSkillMode(Widget &wi, Widget::Action action)
3584 {
3585     if(action != Widget::FocusGained) return;
3586     mnSkillmode = skillmode_t(wi.userValue2().toInt());
3587     Hu_MenuDefaultFocusAction(wi, action);
3588 }
3589 
3590 #if __JDOOM__ || __JHERETIC__
Hu_MenuConfirmInitNewGame(msgresponse_t response,int,void *)3591 static int Hu_MenuConfirmInitNewGame(msgresponse_t response, int /*userValue*/, void * /*context*/)
3592 {
3593     if (response == MSG_YES)
3594     {
3595         Hu_MenuInitNewGame(true);
3596     }
3597     return true;
3598 }
3599 #endif
3600 
3601 /**
3602  * Initialize a new singleplayer game according to the options set via the menu.
3603  * @param confirmed  If @c true this game configuration has already been confirmed.
3604  */
Hu_MenuInitNewGame(bool confirmed)3605 static void Hu_MenuInitNewGame(bool confirmed)
3606 {
3607 #if __JDOOM__ || __JHERETIC__
3608     const int nightmareTextNum = Defs().getTextNum("NIGHTMARE");
3609     if (nightmareTextNum >= 0 && strlen(Defs().text[nightmareTextNum].text) > 0)
3610     {
3611         if (!confirmed && mnSkillmode == SM_NIGHTMARE)
3612         {
3613             Hu_MsgStart(MSG_YESNO, Defs().text[nightmareTextNum].text, Hu_MenuConfirmInitNewGame, 0, NULL);
3614             return;
3615         }
3616     }
3617 #else
3618     DENG2_UNUSED(confirmed);
3619 #endif
3620 
3621     Hu_MenuCommand(chooseCloseMethod());
3622 
3623 #if __JHEXEN__
3624     cfg.playerClass[CONSOLEPLAYER] = playerclass_t(mnPlrClass);
3625 #endif
3626 
3627     GameRules newRules{gfw_DefaultGameRules()};
3628     GameRules_Set(newRules, skill, mnSkillmode);
3629 
3630     Record const &episodeDef = Defs().episodes.find("id", mnEpisode);
3631     G_SetGameActionNewSession(newRules, mnEpisode, de::makeUri(episodeDef.gets("startMap")));
3632 }
3633 
Hu_MenuActionInitNewGame(Widget &,Widget::Action action)3634 void Hu_MenuActionInitNewGame(Widget & /*wi*/, Widget::Action action)
3635 {
3636     if(action != Widget::Deactivated) return;
3637     Hu_MenuInitNewGame(false);
3638 }
3639 
Hu_MenuSelectControlPanelLink(Widget & wi,Widget::Action action)3640 void Hu_MenuSelectControlPanelLink(Widget &wi, Widget::Action action)
3641 {
3642 #define NUM_PANEL_NAMES         1
3643 
3644     static char const *panelNames[NUM_PANEL_NAMES] = {
3645         "taskbar" //,
3646         //"panel audio",
3647         //"panel input"
3648     };
3649 
3650     if(action != Widget::Deactivated) return;
3651 
3652     int idx = wi.userValue2().toInt();
3653     if(idx < 0 || idx > NUM_PANEL_NAMES - 1)
3654     {
3655         idx = 0;
3656     }
3657 
3658     DD_Execute(true, panelNames[idx]);
3659 
3660 #undef NUM_PANEL_NAMES
3661 }
3662 
D_CMD(MenuOpen)3663 D_CMD(MenuOpen)
3664 {
3665     DENG2_UNUSED(src);
3666 
3667     if(argc > 1)
3668     {
3669         if(!qstricmp(argv[1], "open"))
3670         {
3671             Hu_MenuCommand(MCMD_OPEN);
3672             return true;
3673         }
3674         if(!qstricmp(argv[1], "close"))
3675         {
3676             Hu_MenuCommand(MCMD_CLOSE);
3677             return true;
3678         }
3679 
3680         char const *pageName = argv[1];
3681         if(Hu_MenuHasPage(pageName))
3682         {
3683             Hu_MenuCommand(MCMD_OPEN);
3684             Hu_MenuSetPage(pageName);
3685             return true;
3686         }
3687         return false;
3688     }
3689 
3690     Hu_MenuCommand(!menuActive? MCMD_OPEN : MCMD_CLOSE);
3691     return true;
3692 }
3693 
3694 /**
3695  * Routes console commands for menu actions and navigation into the menu subsystem.
3696  */
D_CMD(MenuCommand)3697 D_CMD(MenuCommand)
3698 {
3699     DENG2_UNUSED2(src, argc);
3700 
3701     if(menuActive)
3702     {
3703         char const *cmd = argv[0] + 4;
3704         if(!qstricmp(cmd, "up"))
3705         {
3706             Hu_MenuCommand(MCMD_NAV_UP);
3707             return true;
3708         }
3709         if(!qstricmp(cmd, "down"))
3710         {
3711             Hu_MenuCommand(MCMD_NAV_DOWN);
3712             return true;
3713         }
3714         if(!qstricmp(cmd, "left"))
3715         {
3716             Hu_MenuCommand(MCMD_NAV_LEFT);
3717             return true;
3718         }
3719         if(!qstricmp(cmd, "right"))
3720         {
3721             Hu_MenuCommand(MCMD_NAV_RIGHT);
3722             return true;
3723         }
3724         if(!qstricmp(cmd, "back"))
3725         {
3726             Hu_MenuCommand(MCMD_NAV_OUT);
3727             return true;
3728         }
3729         if(!qstricmp(cmd, "delete"))
3730         {
3731             Hu_MenuCommand(MCMD_DELETE);
3732             return true;
3733         }
3734         if(!qstricmp(cmd, "select"))
3735         {
3736             Hu_MenuCommand(MCMD_SELECT);
3737             return true;
3738         }
3739         if(!qstricmp(cmd, "pagedown"))
3740         {
3741             Hu_MenuCommand(MCMD_NAV_PAGEDOWN);
3742             return true;
3743         }
3744         if(!qstricmp(cmd, "pageup"))
3745         {
3746             Hu_MenuCommand(MCMD_NAV_PAGEUP);
3747             return true;
3748         }
3749     }
3750     return false;
3751 }
3752 
Hu_MenuConsoleRegister()3753 void Hu_MenuConsoleRegister()
3754 {
3755     C_VAR_FLOAT("menu-scale",               &cfg.common.menuScale,              0, .1f, 1);
3756     C_VAR_BYTE ("menu-stretch",             &cfg.common.menuScaleMode,          0, SCALEMODE_FIRST, SCALEMODE_LAST);
3757     C_VAR_FLOAT("menu-flash-r",             &cfg.common.menuTextFlashColor[CR], 0, 0, 1);
3758     C_VAR_FLOAT("menu-flash-g",             &cfg.common.menuTextFlashColor[CG], 0, 0, 1);
3759     C_VAR_FLOAT("menu-flash-b",             &cfg.common.menuTextFlashColor[CB], 0, 0, 1);
3760     C_VAR_INT  ("menu-flash-speed",         &cfg.common.menuTextFlashSpeed,     0, 0, 50);
3761     C_VAR_BYTE ("menu-cursor-rotate",       &cfg.common.menuCursorRotate,       0, 0, 1);
3762     C_VAR_INT  ("menu-effect",              &cfg.common.menuEffectFlags,        0, 0, MEF_EVERYTHING);
3763     C_VAR_FLOAT("menu-color-r",             &cfg.common.menuTextColors[0][CR],  0, 0, 1);
3764     C_VAR_FLOAT("menu-color-g",             &cfg.common.menuTextColors[0][CG],  0, 0, 1);
3765     C_VAR_FLOAT("menu-color-b",             &cfg.common.menuTextColors[0][CB],  0, 0, 1);
3766     C_VAR_FLOAT("menu-colorb-r",            &cfg.common.menuTextColors[1][CR],  0, 0, 1);
3767     C_VAR_FLOAT("menu-colorb-g",            &cfg.common.menuTextColors[1][CG],  0, 0, 1);
3768     C_VAR_FLOAT("menu-colorb-b",            &cfg.common.menuTextColors[1][CB],  0, 0, 1);
3769     C_VAR_FLOAT("menu-colorc-r",            &cfg.common.menuTextColors[2][CR],  0, 0, 1);
3770     C_VAR_FLOAT("menu-colorc-g",            &cfg.common.menuTextColors[2][CG],  0, 0, 1);
3771     C_VAR_FLOAT("menu-colorc-b",            &cfg.common.menuTextColors[2][CB],  0, 0, 1);
3772     C_VAR_FLOAT("menu-colord-r",            &cfg.common.menuTextColors[3][CR],  0, 0, 1);
3773     C_VAR_FLOAT("menu-colord-g",            &cfg.common.menuTextColors[3][CG],  0, 0, 1);
3774     C_VAR_FLOAT("menu-colord-b",            &cfg.common.menuTextColors[3][CB],  0, 0, 1);
3775     C_VAR_FLOAT("menu-glitter",             &cfg.common.menuTextGlitter,        0, 0, 1);
3776     C_VAR_INT  ("menu-fog",                 &cfg.common.hudFog,                 0, 0, 5);
3777     C_VAR_FLOAT("menu-shadow",              &cfg.common.menuShadow,             0, 0, 1);
3778     C_VAR_INT  ("menu-patch-replacement",   &cfg.common.menuPatchReplaceMode,   0, 0, 1);
3779     C_VAR_BYTE ("menu-slam",                &cfg.common.menuSlam,               0, 0, 1);
3780     C_VAR_BYTE ("menu-hotkeys",             &cfg.common.menuShortcutsEnabled,   0, 0, 1);
3781 #if __JDOOM__ || __JDOOM64__
3782     C_VAR_INT  ("menu-quitsound",           &cfg.menuQuitSound,          0, 0, 1);
3783 #endif
3784     C_VAR_BYTE ("menu-save-suggestname",    &cfg.common.menuGameSaveSuggestDescription, 0, 0, 1);
3785 
3786     C_CMD("menu",           "s",    MenuOpen);
3787     C_CMD("menu",           "",     MenuOpen);
3788     C_CMD("menuup",         "",     MenuCommand);
3789     C_CMD("menudown",       "",     MenuCommand);
3790     C_CMD("menupageup",     "",     MenuCommand);
3791     C_CMD("menupagedown",   "",     MenuCommand);
3792     C_CMD("menuleft",       "",     MenuCommand);
3793     C_CMD("menuright",      "",     MenuCommand);
3794     C_CMD("menuselect",     "",     MenuCommand);
3795     C_CMD("menudelete",     "",     MenuCommand);
3796     C_CMD("menuback",       "",     MenuCommand);
3797 }
3798 
3799 } // namespace common
3800