1 /*
2 ** menudef.cpp
3 ** MENUDEF parser amd menu generation code
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 2010 Christoph Oelckers
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 ** notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 ** notice, this list of conditions and the following disclaimer in the
17 ** documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 ** derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34 #include <float.h>
35
36 #include "menu/menu.h"
37 #include "c_dispatch.h"
38 #include "w_wad.h"
39 #include "sc_man.h"
40 #include "v_font.h"
41 #include "g_level.h"
42 #include "d_player.h"
43 #include "v_video.h"
44 #include "i_system.h"
45 #include "c_bind.h"
46 #include "v_palette.h"
47 #include "d_event.h"
48 #include "d_gui.h"
49 #include "i_music.h"
50 #include "m_joy.h"
51 #include "gi.h"
52 #include "i_sound.h"
53
54 #include "optionmenuitems.h"
55
56 void ClearSaveGames();
57
58 MenuDescriptorList MenuDescriptors;
59 static FListMenuDescriptor DefaultListMenuSettings; // contains common settings for all list menus
60 static FOptionMenuDescriptor DefaultOptionMenuSettings; // contains common settings for all Option menus
61 FOptionMenuSettings OptionSettings;
62 FOptionMap OptionValues;
63
64 void I_BuildALDeviceList(FOptionValues *opt);
65
DeinitMenus()66 static void DeinitMenus()
67 {
68 {
69 MenuDescriptorList::Iterator it(MenuDescriptors);
70
71 MenuDescriptorList::Pair *pair;
72
73 while (it.NextPair(pair))
74 {
75 delete pair->Value;
76 pair->Value = NULL;
77 }
78 }
79
80 {
81 FOptionMap::Iterator it(OptionValues);
82
83 FOptionMap::Pair *pair;
84
85 while (it.NextPair(pair))
86 {
87 delete pair->Value;
88 pair->Value = NULL;
89 }
90 }
91 MenuDescriptors.Clear();
92 OptionValues.Clear();
93 DMenu::CurrentMenu = NULL;
94 DefaultListMenuSettings.mItems.Clear();
95 ClearSaveGames();
96 }
97
GetMenuTexture(const char * const name)98 static FTextureID GetMenuTexture(const char* const name)
99 {
100 const FTextureID texture = TexMan.CheckForTexture(name, FTexture::TEX_MiscPatch);
101
102 if (!texture.Exists())
103 {
104 Printf("Missing menu texture: \"%s\"\n", name);
105 }
106
107 return texture;
108 }
109
110 //=============================================================================
111 //
112 //
113 //
114 //=============================================================================
115
SkipSubBlock(FScanner & sc)116 static void SkipSubBlock(FScanner &sc)
117 {
118 sc.MustGetStringName("{");
119 int depth = 1;
120 while (depth > 0)
121 {
122 sc.MustGetString();
123 if (sc.Compare("{")) depth++;
124 if (sc.Compare("}")) depth--;
125 }
126 }
127
128 //=============================================================================
129 //
130 //
131 //
132 //=============================================================================
133
CheckSkipGameBlock(FScanner & sc)134 static bool CheckSkipGameBlock(FScanner &sc)
135 {
136 bool filter = false;
137 sc.MustGetStringName("(");
138 do
139 {
140 sc.MustGetString();
141 filter |= CheckGame(sc.String, false);
142 }
143 while (sc.CheckString(","));
144 sc.MustGetStringName(")");
145 if (!filter)
146 {
147 SkipSubBlock(sc);
148 return true;
149 }
150 return false;
151 }
152
153 //=============================================================================
154 //
155 //
156 //
157 //=============================================================================
158
CheckSkipOptionBlock(FScanner & sc)159 static bool CheckSkipOptionBlock(FScanner &sc)
160 {
161 bool filter = false;
162 sc.MustGetStringName("(");
163 do
164 {
165 sc.MustGetString();
166 if (sc.Compare("ReadThis")) filter |= gameinfo.drawreadthis;
167 else if (sc.Compare("Swapmenu")) filter |= gameinfo.swapmenu;
168 else if (sc.Compare("Windows"))
169 {
170 #ifdef _WIN32
171 filter = true;
172 #endif
173 }
174 else if (sc.Compare("unix"))
175 {
176 #ifdef __unix__
177 filter = true;
178 #endif
179 }
180 else if (sc.Compare("Mac"))
181 {
182 #ifdef __APPLE__
183 filter = true;
184 #endif
185 }
186 else if (sc.Compare("OpenAL"))
187 {
188 filter |= IsOpenALPresent();
189 }
190 else if (sc.Compare("FModEx"))
191 {
192 filter |= IsFModExPresent();
193 }
194 }
195 while (sc.CheckString(","));
196 sc.MustGetStringName(")");
197 if (!filter)
198 {
199 SkipSubBlock(sc);
200 return !sc.CheckString("else");
201 }
202 return false;
203 }
204
205 //=============================================================================
206 //
207 //
208 //
209 //=============================================================================
210
ParseListMenuBody(FScanner & sc,FListMenuDescriptor * desc)211 static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc)
212 {
213 sc.MustGetStringName("{");
214 while (!sc.CheckString("}"))
215 {
216 sc.MustGetString();
217 if (sc.Compare("else"))
218 {
219 SkipSubBlock(sc);
220 }
221 else if (sc.Compare("ifgame"))
222 {
223 if (!CheckSkipGameBlock(sc))
224 {
225 // recursively parse sub-block
226 ParseListMenuBody(sc, desc);
227 }
228 }
229 else if (sc.Compare("ifoption"))
230 {
231 if (!CheckSkipOptionBlock(sc))
232 {
233 // recursively parse sub-block
234 ParseListMenuBody(sc, desc);
235 }
236 }
237 else if (sc.Compare("Class"))
238 {
239 sc.MustGetString();
240 const PClass *cls = PClass::FindClass(sc.String);
241 if (cls == NULL || !cls->IsDescendantOf(RUNTIME_CLASS(DListMenu)))
242 {
243 sc.ScriptError("Unknown menu class '%s'", sc.String);
244 }
245 desc->mClass = cls;
246 }
247 else if (sc.Compare("Selector"))
248 {
249 sc.MustGetString();
250 desc->mSelector = GetMenuTexture(sc.String);
251 sc.MustGetStringName(",");
252 sc.MustGetNumber();
253 desc->mSelectOfsX = sc.Number;
254 sc.MustGetStringName(",");
255 sc.MustGetNumber();
256 desc->mSelectOfsY = sc.Number;
257 }
258 else if (sc.Compare("Linespacing"))
259 {
260 sc.MustGetNumber();
261 desc->mLinespacing = sc.Number;
262 }
263 else if (sc.Compare("Position"))
264 {
265 sc.MustGetNumber();
266 desc->mXpos = sc.Number;
267 sc.MustGetStringName(",");
268 sc.MustGetNumber();
269 desc->mYpos = sc.Number;
270 }
271 else if (sc.Compare("Centermenu"))
272 {
273 desc->mCenter = true;
274 }
275 else if (sc.Compare("MouseWindow"))
276 {
277 sc.MustGetNumber();
278 desc->mWLeft = sc.Number;
279 sc.MustGetStringName(",");
280 sc.MustGetNumber();
281 desc->mWRight = sc.Number;
282 }
283 else if (sc.Compare("StaticPatch") || sc.Compare("StaticPatchCentered"))
284 {
285 bool centered = sc.Compare("StaticPatchCentered");
286 sc.MustGetNumber();
287 int x = sc.Number;
288 sc.MustGetStringName(",");
289 sc.MustGetNumber();
290 int y = sc.Number;
291 sc.MustGetStringName(",");
292 sc.MustGetString();
293 FTextureID tex = GetMenuTexture(sc.String);
294
295 FListMenuItem *it = new FListMenuItemStaticPatch(x, y, tex, centered);
296 desc->mItems.Push(it);
297 }
298 else if (sc.Compare("StaticText") || sc.Compare("StaticTextCentered"))
299 {
300 bool centered = sc.Compare("StaticTextCentered");
301 sc.MustGetNumber();
302 int x = sc.Number;
303 sc.MustGetStringName(",");
304 sc.MustGetNumber();
305 int y = sc.Number;
306 sc.MustGetStringName(",");
307 sc.MustGetString();
308 FListMenuItem *it = new FListMenuItemStaticText(x, y, sc.String, desc->mFont, desc->mFontColor, centered);
309 desc->mItems.Push(it);
310 }
311 else if (sc.Compare("PatchItem"))
312 {
313 sc.MustGetString();
314 FTextureID tex = GetMenuTexture(sc.String);
315 sc.MustGetStringName(",");
316 sc.MustGetString();
317 int hotkey = sc.String[0];
318 sc.MustGetStringName(",");
319 sc.MustGetString();
320 FName action = sc.String;
321 int param = 0;
322 if (sc.CheckString(","))
323 {
324 sc.MustGetNumber();
325 param = sc.Number;
326 }
327
328 FListMenuItem *it = new FListMenuItemPatch(desc->mXpos, desc->mYpos, desc->mLinespacing, hotkey, tex, action, param);
329 desc->mItems.Push(it);
330 desc->mYpos += desc->mLinespacing;
331 if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1;
332 }
333 else if (sc.Compare("TextItem"))
334 {
335 sc.MustGetString();
336 FString text = sc.String;
337 sc.MustGetStringName(",");
338 sc.MustGetString();
339 int hotkey = sc.String[0];
340 sc.MustGetStringName(",");
341 sc.MustGetString();
342 FName action = sc.String;
343 int param = 0;
344 if (sc.CheckString(","))
345 {
346 sc.MustGetNumber();
347 param = sc.Number;
348 }
349
350 FListMenuItem *it = new FListMenuItemText(desc->mXpos, desc->mYpos, desc->mLinespacing, hotkey, text, desc->mFont, desc->mFontColor, desc->mFontColor2, action, param);
351 desc->mItems.Push(it);
352 desc->mYpos += desc->mLinespacing;
353 if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1;
354
355 }
356 else if (sc.Compare("Font"))
357 {
358 sc.MustGetString();
359 FFont *newfont = V_GetFont(sc.String);
360 if (newfont != NULL) desc->mFont = newfont;
361 if (sc.CheckString(","))
362 {
363 sc.MustGetString();
364 desc->mFontColor2 = desc->mFontColor = V_FindFontColor((FName)sc.String);
365 if (sc.CheckString(","))
366 {
367 sc.MustGetString();
368 desc->mFontColor2 = V_FindFontColor((FName)sc.String);
369 }
370 }
371 else
372 {
373 desc->mFontColor = OptionSettings.mFontColor;
374 desc->mFontColor2 = OptionSettings.mFontColorValue;
375 }
376 }
377 else if (sc.Compare("NetgameMessage"))
378 {
379 sc.MustGetString();
380 desc->mNetgameMessage = sc.String;
381 }
382 else if (sc.Compare("PlayerDisplay"))
383 {
384 bool noportrait = false;
385 FName action = NAME_None;
386 sc.MustGetNumber();
387 int x = sc.Number;
388 sc.MustGetStringName(",");
389 sc.MustGetNumber();
390 int y = sc.Number;
391 sc.MustGetStringName(",");
392 sc.MustGetString();
393 PalEntry c1 = V_GetColor(NULL, sc.String);
394 sc.MustGetStringName(",");
395 sc.MustGetString();
396 PalEntry c2 = V_GetColor(NULL, sc.String);
397 if (sc.CheckString(","))
398 {
399 sc.MustGetNumber();
400 noportrait = !!sc.Number;
401 if (sc.CheckString(","))
402 {
403 sc.MustGetString();
404 action = sc.String;
405 }
406 }
407 FListMenuItemPlayerDisplay *it = new FListMenuItemPlayerDisplay(desc, x, y, c1, c2, noportrait, action);
408 desc->mItems.Push(it);
409 }
410 else if (sc.Compare("PlayerNameBox"))
411 {
412 sc.MustGetString();
413 FString text = sc.String;
414 sc.MustGetStringName(",");
415 sc.MustGetNumber();
416 int ofs = sc.Number;
417 sc.MustGetStringName(",");
418 sc.MustGetString();
419 FListMenuItem *it = new FPlayerNameBox(desc->mXpos, desc->mYpos, desc->mLinespacing, ofs, text, desc->mFont, desc->mFontColor, sc.String);
420 desc->mItems.Push(it);
421 desc->mYpos += desc->mLinespacing;
422 if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1;
423 }
424 else if (sc.Compare("ValueText"))
425 {
426 sc.MustGetString();
427 FString text = sc.String;
428 sc.MustGetStringName(",");
429 sc.MustGetString();
430 FName action = sc.String;
431 FName values;
432 if (sc.CheckString(","))
433 {
434 sc.MustGetString();
435 values = sc.String;
436 }
437 FListMenuItem *it = new FValueTextItem(desc->mXpos, desc->mYpos, desc->mLinespacing, text, desc->mFont, desc->mFontColor, desc->mFontColor2, action, values);
438 desc->mItems.Push(it);
439 desc->mYpos += desc->mLinespacing;
440 if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1;
441 }
442 else if (sc.Compare("Slider"))
443 {
444 sc.MustGetString();
445 FString text = sc.String;
446 sc.MustGetStringName(",");
447 sc.MustGetString();
448 FString action = sc.String;
449 sc.MustGetStringName(",");
450 sc.MustGetNumber();
451 int min = sc.Number;
452 sc.MustGetStringName(",");
453 sc.MustGetNumber();
454 int max = sc.Number;
455 sc.MustGetStringName(",");
456 sc.MustGetNumber();
457 int step = sc.Number;
458 FListMenuItem *it = new FSliderItem(desc->mXpos, desc->mYpos, desc->mLinespacing, text, desc->mFont, desc->mFontColor, action, min, max, step);
459 desc->mItems.Push(it);
460 desc->mYpos += desc->mLinespacing;
461 if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1;
462 }
463 else
464 {
465 sc.ScriptError("Unknown keyword '%s'", sc.String);
466 }
467 }
468 }
469
470 //=============================================================================
471 //
472 //
473 //
474 //=============================================================================
475
CheckCompatible(FMenuDescriptor * newd,FMenuDescriptor * oldd)476 static bool CheckCompatible(FMenuDescriptor *newd, FMenuDescriptor *oldd)
477 {
478 if (oldd->mClass == NULL) return true;
479 return oldd->mClass == newd->mClass;
480 }
481
ReplaceMenu(FScanner & sc,FMenuDescriptor * desc)482 static bool ReplaceMenu(FScanner &sc, FMenuDescriptor *desc)
483 {
484 FMenuDescriptor **pOld = MenuDescriptors.CheckKey(desc->mMenuName);
485 if (pOld != NULL && *pOld != NULL)
486 {
487 if (CheckCompatible(desc, *pOld))
488 {
489 delete *pOld;
490 }
491 else
492 {
493 sc.ScriptMessage("Tried to replace menu '%s' with a menu of different type", desc->mMenuName.GetChars());
494 return true;
495 }
496 }
497 MenuDescriptors[desc->mMenuName] = desc;
498 return false;
499 }
500
501 //=============================================================================
502 //
503 //
504 //
505 //=============================================================================
506
ParseListMenu(FScanner & sc)507 static void ParseListMenu(FScanner &sc)
508 {
509 sc.MustGetString();
510
511 FListMenuDescriptor *desc = new FListMenuDescriptor;
512 desc->mType = MDESC_ListMenu;
513 desc->mMenuName = sc.String;
514 desc->mSelectedItem = -1;
515 desc->mAutoselect = -1;
516 desc->mSelectOfsX = DefaultListMenuSettings.mSelectOfsX;
517 desc->mSelectOfsY = DefaultListMenuSettings.mSelectOfsY;
518 desc->mSelector = DefaultListMenuSettings.mSelector;
519 desc->mDisplayTop = DefaultListMenuSettings.mDisplayTop;
520 desc->mXpos = DefaultListMenuSettings.mXpos;
521 desc->mYpos = DefaultListMenuSettings.mYpos;
522 desc->mLinespacing = DefaultListMenuSettings.mLinespacing;
523 desc->mNetgameMessage = DefaultListMenuSettings.mNetgameMessage;
524 desc->mFont = DefaultListMenuSettings.mFont;
525 desc->mFontColor = DefaultListMenuSettings.mFontColor;
526 desc->mFontColor2 = DefaultListMenuSettings.mFontColor2;
527 desc->mClass = NULL;
528 desc->mRedirect = NULL;
529 desc->mWLeft = 0;
530 desc->mWRight = 0;
531 desc->mCenter = false;
532
533 ParseListMenuBody(sc, desc);
534 bool scratch = ReplaceMenu(sc, desc);
535 if (scratch) delete desc;
536 }
537
538 //=============================================================================
539 //
540 //
541 //
542 //=============================================================================
543
ParseOptionValue(FScanner & sc)544 static void ParseOptionValue(FScanner &sc)
545 {
546 FName optname;
547
548 FOptionValues *val = new FOptionValues;
549 sc.MustGetString();
550 optname = sc.String;
551 sc.MustGetStringName("{");
552 while (!sc.CheckString("}"))
553 {
554 FOptionValues::Pair &pair = val->mValues[val->mValues.Reserve(1)];
555 sc.MustGetFloat();
556 pair.Value = sc.Float;
557 sc.MustGetStringName(",");
558 sc.MustGetString();
559 pair.Text = strbin1(sc.String);
560 }
561 FOptionValues **pOld = OptionValues.CheckKey(optname);
562 if (pOld != NULL && *pOld != NULL)
563 {
564 delete *pOld;
565 }
566 OptionValues[optname] = val;
567 }
568
569
570 //=============================================================================
571 //
572 //
573 //
574 //=============================================================================
575
ParseOptionString(FScanner & sc)576 static void ParseOptionString(FScanner &sc)
577 {
578 FName optname;
579
580 FOptionValues *val = new FOptionValues;
581 sc.MustGetString();
582 optname = sc.String;
583 sc.MustGetStringName("{");
584 while (!sc.CheckString("}"))
585 {
586 FOptionValues::Pair &pair = val->mValues[val->mValues.Reserve(1)];
587 sc.MustGetString();
588 pair.Value = DBL_MAX;
589 pair.TextValue = sc.String;
590 sc.MustGetStringName(",");
591 sc.MustGetString();
592 pair.Text = strbin1(sc.String);
593 }
594 FOptionValues **pOld = OptionValues.CheckKey(optname);
595 if (pOld != NULL && *pOld != NULL)
596 {
597 delete *pOld;
598 }
599 OptionValues[optname] = val;
600 }
601
602
603 //=============================================================================
604 //
605 //
606 //
607 //=============================================================================
608
ParseOptionSettings(FScanner & sc)609 static void ParseOptionSettings(FScanner &sc)
610 {
611 sc.MustGetStringName("{");
612 while (!sc.CheckString("}"))
613 {
614 sc.MustGetString();
615 if (sc.Compare("else"))
616 {
617 SkipSubBlock(sc);
618 }
619 else if (sc.Compare("ifgame"))
620 {
621 if (!CheckSkipGameBlock(sc))
622 {
623 // recursively parse sub-block
624 ParseOptionSettings(sc);
625 }
626 }
627 else if (sc.Compare("Linespacing"))
628 {
629 sc.MustGetNumber();
630 OptionSettings.mLinespacing = sc.Number;
631 }
632 else if (sc.Compare("LabelOffset"))
633 {
634 sc.MustGetNumber();
635 // ignored
636 }
637 else
638 {
639 sc.ScriptError("Unknown keyword '%s'", sc.String);
640 }
641 }
642 }
643
644 //=============================================================================
645 //
646 //
647 //
648 //=============================================================================
649
ParseOptionMenuBody(FScanner & sc,FOptionMenuDescriptor * desc)650 static void ParseOptionMenuBody(FScanner &sc, FOptionMenuDescriptor *desc)
651 {
652 sc.MustGetStringName("{");
653 while (!sc.CheckString("}"))
654 {
655 sc.MustGetString();
656 if (sc.Compare("else"))
657 {
658 SkipSubBlock(sc);
659 }
660 else if (sc.Compare("ifgame"))
661 {
662 if (!CheckSkipGameBlock(sc))
663 {
664 // recursively parse sub-block
665 ParseOptionMenuBody(sc, desc);
666 }
667 }
668 else if (sc.Compare("ifoption"))
669 {
670 if (!CheckSkipOptionBlock(sc))
671 {
672 // recursively parse sub-block
673 ParseOptionMenuBody(sc, desc);
674 }
675 }
676 else if (sc.Compare("Class"))
677 {
678 sc.MustGetString();
679 const PClass *cls = PClass::FindClass(sc.String);
680 if (cls == NULL || !cls->IsDescendantOf(RUNTIME_CLASS(DOptionMenu)))
681 {
682 sc.ScriptError("Unknown menu class '%s'", sc.String);
683 }
684 desc->mClass = cls;
685 }
686 else if (sc.Compare("Title"))
687 {
688 sc.MustGetString();
689 desc->mTitle = sc.String;
690 }
691 else if (sc.Compare("Position"))
692 {
693 sc.MustGetNumber();
694 desc->mPosition = sc.Number;
695 }
696 else if (sc.Compare("DefaultSelection"))
697 {
698 sc.MustGetNumber();
699 desc->mSelectedItem = sc.Number;
700 }
701 else if (sc.Compare("ScrollTop"))
702 {
703 sc.MustGetNumber();
704 desc->mScrollTop = sc.Number;
705 }
706 else if (sc.Compare("Indent"))
707 {
708 sc.MustGetNumber();
709 desc->mIndent = sc.Number;
710 }
711 else if (sc.Compare("Submenu"))
712 {
713 sc.MustGetString();
714 FString label = sc.String;
715 sc.MustGetStringName(",");
716 sc.MustGetString();
717 FOptionMenuItem *it = new FOptionMenuItemSubmenu(label, sc.String);
718 desc->mItems.Push(it);
719 }
720 else if (sc.Compare("Option"))
721 {
722 sc.MustGetString();
723 FString label = sc.String;
724 sc.MustGetStringName(",");
725 sc.MustGetString();
726 FString cvar = sc.String;
727 sc.MustGetStringName(",");
728 sc.MustGetString();
729 FString values = sc.String;
730 FString check;
731 int center = 0;
732 if (sc.CheckString(","))
733 {
734 sc.MustGetString();
735 if (*sc.String != 0) check = sc.String;
736 if (sc.CheckString(","))
737 {
738 sc.MustGetNumber();
739 center = sc.Number;
740 }
741 }
742 FOptionMenuItem *it = new FOptionMenuItemOption(label, cvar, values, check, center);
743 desc->mItems.Push(it);
744 }
745 else if (sc.Compare("Command"))
746 {
747 sc.MustGetString();
748 FString label = sc.String;
749 sc.MustGetStringName(",");
750 sc.MustGetString();
751 FOptionMenuItem *it = new FOptionMenuItemCommand(label, sc.String);
752 desc->mItems.Push(it);
753 }
754 else if (sc.Compare("SafeCommand"))
755 {
756 sc.MustGetString();
757 FString label = sc.String;
758 sc.MustGetStringName(",");
759 sc.MustGetString();
760 FOptionMenuItem *it = new FOptionMenuItemSafeCommand(label, sc.String);
761 desc->mItems.Push(it);
762 }
763 else if (sc.Compare("Control") || sc.Compare("MapControl"))
764 {
765 bool map = sc.Compare("MapControl");
766 sc.MustGetString();
767 FString label = sc.String;
768 sc.MustGetStringName(",");
769 sc.MustGetString();
770 FOptionMenuItem *it = new FOptionMenuItemControl(label, sc.String, map? &AutomapBindings : &Bindings);
771 desc->mItems.Push(it);
772 }
773 else if (sc.Compare("ColorPicker"))
774 {
775 sc.MustGetString();
776 FString label = sc.String;
777 sc.MustGetStringName(",");
778 sc.MustGetString();
779 FOptionMenuItem *it = new FOptionMenuItemColorPicker(label, sc.String);
780 desc->mItems.Push(it);
781 }
782 else if (sc.Compare("StaticText"))
783 {
784 sc.MustGetString();
785 FString label = sc.String;
786 bool cr = false;
787 if (sc.CheckString(","))
788 {
789 sc.MustGetNumber();
790 cr = !!sc.Number;
791 }
792 FOptionMenuItem *it = new FOptionMenuItemStaticText(label, cr);
793 desc->mItems.Push(it);
794 }
795 else if (sc.Compare("StaticTextSwitchable"))
796 {
797 sc.MustGetString();
798 FString label = sc.String;
799 sc.MustGetStringName(",");
800 sc.MustGetString();
801 FString label2 = sc.String;
802 sc.MustGetStringName(",");
803 sc.MustGetString();
804 FName action = sc.String;
805 bool cr = false;
806 if (sc.CheckString(","))
807 {
808 sc.MustGetNumber();
809 cr = !!sc.Number;
810 }
811 FOptionMenuItem *it = new FOptionMenuItemStaticTextSwitchable(label, label2, action, cr);
812 desc->mItems.Push(it);
813 }
814 else if (sc.Compare("Slider"))
815 {
816 sc.MustGetString();
817 FString text = sc.String;
818 sc.MustGetStringName(",");
819 sc.MustGetString();
820 FString action = sc.String;
821 sc.MustGetStringName(",");
822 sc.MustGetFloat();
823 double min = sc.Float;
824 sc.MustGetStringName(",");
825 sc.MustGetFloat();
826 double max = sc.Float;
827 sc.MustGetStringName(",");
828 sc.MustGetFloat();
829 double step = sc.Float;
830 int showvalue = 1;
831 if (sc.CheckString(","))
832 {
833 sc.MustGetNumber();
834 showvalue = sc.Number;
835 }
836 FOptionMenuItem *it = new FOptionMenuSliderCVar(text, action, min, max, step, showvalue);
837 desc->mItems.Push(it);
838 }
839 else if (sc.Compare("screenresolution"))
840 {
841 sc.MustGetString();
842 FOptionMenuItem *it = new FOptionMenuScreenResolutionLine(sc.String);
843 desc->mItems.Push(it);
844 }
845 // [TP] -- Text input widget
846 else if ( sc.Compare( "TextField" ))
847 {
848 sc.MustGetString();
849 FString label = sc.String;
850 sc.MustGetStringName( "," );
851 sc.MustGetString();
852 FString cvar = sc.String;
853 FString check;
854
855 if ( sc.CheckString( "," ))
856 {
857 sc.MustGetString();
858 check = sc.String;
859 }
860
861 FOptionMenuItem* it = new FOptionMenuTextField( label, cvar, check );
862 desc->mItems.Push( it );
863 }
864 // [TP] -- Number input widget
865 else if ( sc.Compare( "NumberField" ))
866 {
867 sc.MustGetString();
868 FString label = sc.String;
869 sc.MustGetStringName( "," );
870 sc.MustGetString();
871 FString cvar = sc.String;
872 float minimum = 0.0f;
873 float maximum = 100.0f;
874 float step = 1.0f;
875 FString check;
876
877 if ( sc.CheckString( "," ))
878 {
879 sc.MustGetFloat();
880 minimum = (float) sc.Float;
881 sc.MustGetStringName( "," );
882 sc.MustGetFloat();
883 maximum = (float) sc.Float;
884
885 if ( sc.CheckString( "," ))
886 {
887 sc.MustGetFloat();
888 step = (float) sc.Float;
889
890 if ( sc.CheckString( "," ))
891 {
892 sc.MustGetString();
893 check = sc.String;
894 }
895 }
896 }
897
898 FOptionMenuItem* it = new FOptionMenuNumberField( label, cvar,
899 minimum, maximum, step, check );
900 desc->mItems.Push( it );
901 }
902 else
903 {
904 sc.ScriptError("Unknown keyword '%s'", sc.String);
905 }
906 }
907 }
908
909 //=============================================================================
910 //
911 //
912 //
913 //=============================================================================
914
ParseOptionMenu(FScanner & sc)915 static void ParseOptionMenu(FScanner &sc)
916 {
917 sc.MustGetString();
918
919 FOptionMenuDescriptor *desc = new FOptionMenuDescriptor;
920 desc->mType = MDESC_OptionsMenu;
921 desc->mMenuName = sc.String;
922 desc->mSelectedItem = -1;
923 desc->mScrollPos = 0;
924 desc->mClass = NULL;
925 desc->mPosition = DefaultOptionMenuSettings.mPosition;
926 desc->mScrollTop = DefaultOptionMenuSettings.mScrollTop;
927 desc->mIndent = DefaultOptionMenuSettings.mIndent;
928 desc->mDontDim = DefaultOptionMenuSettings.mDontDim;
929
930 ParseOptionMenuBody(sc, desc);
931 bool scratch = ReplaceMenu(sc, desc);
932 if (desc->mIndent == 0) desc->CalcIndent();
933 if (scratch) delete desc;
934 }
935
936
937 //=============================================================================
938 //
939 //
940 //
941 //=============================================================================
942
M_ParseMenuDefs()943 void M_ParseMenuDefs()
944 {
945 int lump, lastlump = 0;
946
947 OptionSettings.mTitleColor = V_FindFontColor(gameinfo.mTitleColor);
948 OptionSettings.mFontColor = V_FindFontColor(gameinfo.mFontColor);
949 OptionSettings.mFontColorValue = V_FindFontColor(gameinfo.mFontColorValue);
950 OptionSettings.mFontColorMore = V_FindFontColor(gameinfo.mFontColorMore);
951 OptionSettings.mFontColorHeader = V_FindFontColor(gameinfo.mFontColorHeader);
952 OptionSettings.mFontColorHighlight = V_FindFontColor(gameinfo.mFontColorHighlight);
953 OptionSettings.mFontColorSelection = V_FindFontColor(gameinfo.mFontColorSelection);
954 DefaultListMenuSettings.Reset();
955 DefaultOptionMenuSettings.Reset();
956
957 atterm( DeinitMenus);
958 DeinitMenus();
959 while ((lump = Wads.FindLump ("MENUDEF", &lastlump)) != -1)
960 {
961 FScanner sc(lump);
962
963 sc.SetCMode(true);
964 while (sc.GetString())
965 {
966 if (sc.Compare("LISTMENU"))
967 {
968 ParseListMenu(sc);
969 }
970 else if (sc.Compare("DEFAULTLISTMENU"))
971 {
972 ParseListMenuBody(sc, &DefaultListMenuSettings);
973 if (DefaultListMenuSettings.mItems.Size() > 0)
974 {
975 I_FatalError("You cannot add menu items to the menu default settings.");
976 }
977 }
978 else if (sc.Compare("OPTIONVALUE"))
979 {
980 ParseOptionValue(sc);
981 }
982 else if (sc.Compare("OPTIONSTRING"))
983 {
984 ParseOptionString(sc);
985 }
986 else if (sc.Compare("OPTIONMENUSETTINGS"))
987 {
988 ParseOptionSettings(sc);
989 }
990 else if (sc.Compare("OPTIONMENU"))
991 {
992 ParseOptionMenu(sc);
993 }
994 else if (sc.Compare("DEFAULTOPTIONMENU"))
995 {
996 ParseOptionMenuBody(sc, &DefaultOptionMenuSettings);
997 if (DefaultOptionMenuSettings.mItems.Size() > 0)
998 {
999 I_FatalError("You cannot add menu items to the menu default settings.");
1000 }
1001 }
1002 else
1003 {
1004 sc.ScriptError("Unknown keyword '%s'", sc.String);
1005 }
1006 }
1007 }
1008 }
1009
1010
1011 //=============================================================================
1012 //
1013 // Creates the episode menu
1014 // Falls back on an option menu if there's not enough screen space to show all episodes
1015 //
1016 //=============================================================================
1017
BuildEpisodeMenu()1018 static void BuildEpisodeMenu()
1019 {
1020 // Build episode menu
1021 bool success = false;
1022 FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Episodemenu);
1023 if (desc != NULL)
1024 {
1025 if ((*desc)->mType == MDESC_ListMenu)
1026 {
1027 FListMenuDescriptor *ld = static_cast<FListMenuDescriptor*>(*desc);
1028 int posy = ld->mYpos;
1029 int topy = posy;
1030
1031 // Get lowest y coordinate of any static item in the menu
1032 for(unsigned i = 0; i < ld->mItems.Size(); i++)
1033 {
1034 int y = ld->mItems[i]->GetY();
1035 if (y < topy) topy = y;
1036 }
1037
1038 // center the menu on the screen if the top space is larger than the bottom space
1039 int totalheight = posy + AllEpisodes.Size() * ld->mLinespacing - topy;
1040
1041 if (totalheight < 190 || AllEpisodes.Size() == 1)
1042 {
1043 int newtop = (200 - totalheight + topy) / 2;
1044 int topdelta = newtop - topy;
1045 if (topdelta < 0)
1046 {
1047 for(unsigned i = 0; i < ld->mItems.Size(); i++)
1048 {
1049 ld->mItems[i]->OffsetPositionY(topdelta);
1050 }
1051 posy -= topdelta;
1052 }
1053
1054 ld->mSelectedItem = ld->mItems.Size();
1055 for(unsigned i = 0; i < AllEpisodes.Size(); i++)
1056 {
1057 FListMenuItem *it;
1058 if (AllEpisodes[i].mPicName.IsNotEmpty())
1059 {
1060 FTextureID tex = GetMenuTexture(AllEpisodes[i].mPicName);
1061 it = new FListMenuItemPatch(ld->mXpos, posy, ld->mLinespacing, AllEpisodes[i].mShortcut,
1062 tex, NAME_Skillmenu, i);
1063 }
1064 else
1065 {
1066 it = new FListMenuItemText(ld->mXpos, posy, ld->mLinespacing, AllEpisodes[i].mShortcut,
1067 AllEpisodes[i].mEpisodeName, ld->mFont, ld->mFontColor, ld->mFontColor2, NAME_Skillmenu, i);
1068 }
1069 ld->mItems.Push(it);
1070 posy += ld->mLinespacing;
1071 }
1072 if (AllEpisodes.Size() == 1)
1073 {
1074 ld->mAutoselect = ld->mSelectedItem;
1075 }
1076 success = true;
1077 }
1078 }
1079 }
1080 if (!success)
1081 {
1082 // Couldn't create the episode menu, either because there's too many episodes or some error occured
1083 // Create an option menu for episode selection instead.
1084 FOptionMenuDescriptor *od = new FOptionMenuDescriptor;
1085 if (desc != NULL) delete *desc;
1086 MenuDescriptors[NAME_Episodemenu] = od;
1087 od->mType = MDESC_OptionsMenu;
1088 od->mMenuName = NAME_Episodemenu;
1089 od->mTitle = "$MNU_EPISODE";
1090 od->mSelectedItem = 0;
1091 od->mScrollPos = 0;
1092 od->mClass = NULL;
1093 od->mPosition = -15;
1094 od->mScrollTop = 0;
1095 od->mIndent = 160;
1096 od->mDontDim = false;
1097 for(unsigned i = 0; i < AllEpisodes.Size(); i++)
1098 {
1099 FOptionMenuItemSubmenu *it = new FOptionMenuItemSubmenu(AllEpisodes[i].mEpisodeName, "Skillmenu", i);
1100 od->mItems.Push(it);
1101 }
1102 }
1103 }
1104
1105 //=============================================================================
1106 //
1107 //
1108 //
1109 //=============================================================================
1110
BuildPlayerclassMenu()1111 static void BuildPlayerclassMenu()
1112 {
1113 bool success = false;
1114
1115 // Build player class menu
1116 FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Playerclassmenu);
1117 if (desc != NULL)
1118 {
1119 if ((*desc)->mType == MDESC_ListMenu)
1120 {
1121 FListMenuDescriptor *ld = static_cast<FListMenuDescriptor*>(*desc);
1122 // add player display
1123 ld->mSelectedItem = ld->mItems.Size();
1124
1125 int posy = ld->mYpos;
1126 int topy = posy;
1127
1128 // Get lowest y coordinate of any static item in the menu
1129 for(unsigned i = 0; i < ld->mItems.Size(); i++)
1130 {
1131 int y = ld->mItems[i]->GetY();
1132 if (y < topy) topy = y;
1133 }
1134
1135 // Count the number of items this menu will show
1136 int numclassitems = 0;
1137 for (unsigned i = 0; i < PlayerClasses.Size (); i++)
1138 {
1139 if (!(PlayerClasses[i].Flags & PCF_NOMENU))
1140 {
1141 const char *pname = GetPrintableDisplayName(PlayerClasses[i].Type);
1142 if (pname != NULL)
1143 {
1144 numclassitems++;
1145 }
1146 }
1147 }
1148
1149 // center the menu on the screen if the top space is larger than the bottom space
1150 int totalheight = posy + (numclassitems+1) * ld->mLinespacing - topy;
1151
1152 if (numclassitems <= 1)
1153 {
1154 // create a dummy item that auto-chooses the default class.
1155 FListMenuItemText *it = new FListMenuItemText(0, 0, 0, 'p', "player",
1156 ld->mFont,ld->mFontColor, ld->mFontColor2, NAME_Episodemenu, -1000);
1157 ld->mAutoselect = ld->mItems.Push(it);
1158 success = true;
1159 }
1160 else if (totalheight <= 190)
1161 {
1162 int newtop = (200 - totalheight + topy) / 2;
1163 int topdelta = newtop - topy;
1164 if (topdelta < 0)
1165 {
1166 for(unsigned i = 0; i < ld->mItems.Size(); i++)
1167 {
1168 ld->mItems[i]->OffsetPositionY(topdelta);
1169 }
1170 posy -= topdelta;
1171 }
1172
1173 int n = 0;
1174 for (unsigned i = 0; i < PlayerClasses.Size (); i++)
1175 {
1176 if (!(PlayerClasses[i].Flags & PCF_NOMENU))
1177 {
1178 const char *pname = GetPrintableDisplayName(PlayerClasses[i].Type);
1179 if (pname != NULL)
1180 {
1181 FListMenuItemText *it = new FListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname,
1182 pname, ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, i);
1183 ld->mItems.Push(it);
1184 ld->mYpos += ld->mLinespacing;
1185 n++;
1186 }
1187 }
1188 }
1189 if (n > 1 && !gameinfo.norandomplayerclass)
1190 {
1191 FListMenuItemText *it = new FListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, 'r',
1192 "$MNU_RANDOM", ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, -1);
1193 ld->mItems.Push(it);
1194 }
1195 if (n == 0)
1196 {
1197 const char *pname = GetPrintableDisplayName(PlayerClasses[0].Type);
1198 if (pname != NULL)
1199 {
1200 FListMenuItemText *it = new FListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname,
1201 pname, ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, 0);
1202 ld->mItems.Push(it);
1203 }
1204 }
1205 success = true;
1206 }
1207 }
1208 }
1209 if (!success)
1210 {
1211 // Couldn't create the playerclass menu, either because there's too many episodes or some error occured
1212 // Create an option menu for class selection instead.
1213 FOptionMenuDescriptor *od = new FOptionMenuDescriptor;
1214 if (desc != NULL) delete *desc;
1215 MenuDescriptors[NAME_Playerclassmenu] = od;
1216 od->mType = MDESC_OptionsMenu;
1217 od->mMenuName = NAME_Playerclassmenu;
1218 od->mTitle = "$MNU_CHOOSECLASS";
1219 od->mSelectedItem = 0;
1220 od->mScrollPos = 0;
1221 od->mClass = NULL;
1222 od->mPosition = -15;
1223 od->mScrollTop = 0;
1224 od->mIndent = 160;
1225 od->mDontDim = false;
1226 od->mNetgameMessage = "$NEWGAME";
1227
1228 for (unsigned i = 0; i < PlayerClasses.Size (); i++)
1229 {
1230 if (!(PlayerClasses[i].Flags & PCF_NOMENU))
1231 {
1232 const char *pname = GetPrintableDisplayName(PlayerClasses[i].Type);
1233 if (pname != NULL)
1234 {
1235 FOptionMenuItemSubmenu *it = new FOptionMenuItemSubmenu(pname, "Episodemenu", i);
1236 od->mItems.Push(it);
1237 }
1238 }
1239 }
1240 FOptionMenuItemSubmenu *it = new FOptionMenuItemSubmenu("Random", "Episodemenu", -1);
1241 od->mItems.Push(it);
1242 }
1243 }
1244
1245 //=============================================================================
1246 //
1247 // Reads any XHAIRS lumps for the names of crosshairs and
1248 // adds them to the display options menu.
1249 //
1250 //=============================================================================
1251
InitCrosshairsList()1252 static void InitCrosshairsList()
1253 {
1254 int lastlump, lump;
1255
1256 lastlump = 0;
1257
1258 FOptionValues **opt = OptionValues.CheckKey(NAME_Crosshairs);
1259 if (opt == NULL)
1260 {
1261 return; // no crosshair value list present. No need to go on.
1262 }
1263
1264 FOptionValues::Pair *pair = &(*opt)->mValues[(*opt)->mValues.Reserve(1)];
1265 pair->Value = 0;
1266 pair->Text = "None";
1267
1268 while ((lump = Wads.FindLump("XHAIRS", &lastlump)) != -1)
1269 {
1270 FScanner sc(lump);
1271 while (sc.GetNumber())
1272 {
1273 FOptionValues::Pair value;
1274 value.Value = sc.Number;
1275 sc.MustGetString();
1276 value.Text = sc.String;
1277 if (value.Value != 0)
1278 { // Check if it already exists. If not, add it.
1279 unsigned int i;
1280
1281 for (i = 1; i < (*opt)->mValues.Size(); ++i)
1282 {
1283 if ((*opt)->mValues[i].Value == value.Value)
1284 {
1285 break;
1286 }
1287 }
1288 if (i < (*opt)->mValues.Size())
1289 {
1290 (*opt)->mValues[i].Text = value.Text;
1291 }
1292 else
1293 {
1294 (*opt)->mValues.Push(value);
1295 }
1296 }
1297 }
1298 }
1299 }
1300
1301 //=============================================================================
1302 //
1303 // With the current workings of the menu system this cannot be done any longer
1304 // from within the respective CCMDs.
1305 //
1306 //=============================================================================
1307
InitKeySections()1308 static void InitKeySections()
1309 {
1310 FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_CustomizeControls);
1311 if (desc != NULL)
1312 {
1313 if ((*desc)->mType == MDESC_OptionsMenu)
1314 {
1315 FOptionMenuDescriptor *menu = static_cast<FOptionMenuDescriptor*>(*desc);
1316
1317 for (unsigned i = 0; i < KeySections.Size(); i++)
1318 {
1319 FKeySection *sect = &KeySections[i];
1320 FOptionMenuItem *item = new FOptionMenuItemStaticText(" ", false);
1321 menu->mItems.Push(item);
1322 item = new FOptionMenuItemStaticText(sect->mTitle, true);
1323 menu->mItems.Push(item);
1324 for (unsigned j = 0; j < sect->mActions.Size(); j++)
1325 {
1326 FKeyAction *act = §->mActions[j];
1327 item = new FOptionMenuItemControl(act->mTitle, act->mAction, &Bindings);
1328 menu->mItems.Push(item);
1329 }
1330 }
1331 }
1332 }
1333 }
1334
1335 //=============================================================================
1336 //
1337 // Special menus will be created once all engine data is loaded
1338 //
1339 //=============================================================================
1340
M_CreateMenus()1341 void M_CreateMenus()
1342 {
1343 BuildEpisodeMenu();
1344 BuildPlayerclassMenu();
1345 InitCrosshairsList();
1346 InitKeySections();
1347
1348 FOptionValues **opt = OptionValues.CheckKey(NAME_Mididevices);
1349 if (opt != NULL)
1350 {
1351 I_BuildMIDIMenuList(*opt);
1352 }
1353 opt = OptionValues.CheckKey(NAME_Aldevices);
1354 if (opt != NULL)
1355 {
1356 I_BuildALDeviceList(*opt);
1357 }
1358 }
1359
1360 //=============================================================================
1361 //
1362 // The skill menu must be refeshed each time it starts up
1363 //
1364 //=============================================================================
1365 extern int restart;
1366
M_StartupSkillMenu(FGameStartup * gs)1367 void M_StartupSkillMenu(FGameStartup *gs)
1368 {
1369 static int done = -1;
1370 bool success = false;
1371 FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Skillmenu);
1372 if (desc != NULL)
1373 {
1374 if ((*desc)->mType == MDESC_ListMenu)
1375 {
1376 FListMenuDescriptor *ld = static_cast<FListMenuDescriptor*>(*desc);
1377 int x = ld->mXpos;
1378 int y = ld->mYpos;
1379
1380 // Delete previous contents
1381 for(unsigned i=0; i<ld->mItems.Size(); i++)
1382 {
1383 FName n = ld->mItems[i]->GetAction(NULL);
1384 if (n == NAME_Startgame || n == NAME_StartgameConfirm)
1385 {
1386 for(unsigned j=i; j<ld->mItems.Size(); j++)
1387 {
1388 delete ld->mItems[j];
1389 }
1390 ld->mItems.Resize(i);
1391 break;
1392 }
1393 }
1394
1395 if (done != restart)
1396 {
1397 done = restart;
1398 int defskill = DefaultSkill;
1399 if ((unsigned int)defskill >= AllSkills.Size())
1400 {
1401 defskill = (AllSkills.Size() - 1) / 2;
1402 }
1403 ld->mSelectedItem = ld->mItems.Size() + defskill;
1404
1405 int posy = y;
1406 int topy = posy;
1407
1408 // Get lowest y coordinate of any static item in the menu
1409 for(unsigned i = 0; i < ld->mItems.Size(); i++)
1410 {
1411 int y = ld->mItems[i]->GetY();
1412 if (y < topy) topy = y;
1413 }
1414
1415 // center the menu on the screen if the top space is larger than the bottom space
1416 int totalheight = posy + AllSkills.Size() * ld->mLinespacing - topy;
1417
1418 if (totalheight < 190 || AllSkills.Size() == 1)
1419 {
1420 int newtop = (200 - totalheight + topy) / 2;
1421 int topdelta = newtop - topy;
1422 if (topdelta < 0)
1423 {
1424 for(unsigned i = 0; i < ld->mItems.Size(); i++)
1425 {
1426 ld->mItems[i]->OffsetPositionY(topdelta);
1427 }
1428 y = ld->mYpos = posy - topdelta;
1429 }
1430 }
1431 else
1432 {
1433 // too large
1434 delete ld;
1435 desc = NULL;
1436 done = false;
1437 goto fail;
1438 }
1439 }
1440
1441 unsigned firstitem = ld->mItems.Size();
1442 for(unsigned int i = 0; i < AllSkills.Size(); i++)
1443 {
1444 FSkillInfo &skill = AllSkills[i];
1445 FListMenuItem *li;
1446 // Using a different name for skills that must be confirmed makes handling this easier.
1447 FName action = (skill.MustConfirm && !AllEpisodes[gs->Episode].mNoSkill) ?
1448 NAME_StartgameConfirm : NAME_Startgame;
1449 FString *pItemText = NULL;
1450 if (gs->PlayerClass != NULL)
1451 {
1452 pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass);
1453 }
1454
1455 if (skill.PicName.Len() != 0 && pItemText == NULL)
1456 {
1457 FTextureID tex = GetMenuTexture(skill.PicName);
1458 li = new FListMenuItemPatch(ld->mXpos, y, ld->mLinespacing, skill.Shortcut, tex, action, i);
1459 }
1460 else
1461 {
1462 EColorRange color = (EColorRange)skill.GetTextColor();
1463 if (color == CR_UNTRANSLATED) color = ld->mFontColor;
1464 li = new FListMenuItemText(x, y, ld->mLinespacing, skill.Shortcut,
1465 pItemText? *pItemText : skill.MenuName, ld->mFont, color,ld->mFontColor2, action, i);
1466 }
1467 ld->mItems.Push(li);
1468 y += ld->mLinespacing;
1469 }
1470 if (AllEpisodes[gs->Episode].mNoSkill || AllSkills.Size() == 1)
1471 {
1472 ld->mAutoselect = firstitem + M_GetDefaultSkill();
1473 }
1474 else
1475 {
1476 ld->mAutoselect = -1;
1477 }
1478 success = true;
1479 }
1480 }
1481 if (success) return;
1482 fail:
1483 // Option menu fallback for overlong skill lists
1484 FOptionMenuDescriptor *od;
1485 if (desc == NULL)
1486 {
1487 od = new FOptionMenuDescriptor;
1488 if (desc != NULL) delete *desc;
1489 MenuDescriptors[NAME_Skillmenu] = od;
1490 od->mType = MDESC_OptionsMenu;
1491 od->mMenuName = NAME_Skillmenu;
1492 od->mTitle = "$MNU_CHOOSESKILL";
1493 od->mSelectedItem = 0;
1494 od->mScrollPos = 0;
1495 od->mClass = NULL;
1496 od->mPosition = -15;
1497 od->mScrollTop = 0;
1498 od->mIndent = 160;
1499 od->mDontDim = false;
1500 }
1501 else
1502 {
1503 od = static_cast<FOptionMenuDescriptor*>(*desc);
1504 for(unsigned i=0;i<od->mItems.Size(); i++)
1505 {
1506 delete od->mItems[i];
1507 }
1508 od->mItems.Clear();
1509 }
1510 for(unsigned int i = 0; i < AllSkills.Size(); i++)
1511 {
1512 FSkillInfo &skill = AllSkills[i];
1513 FOptionMenuItem *li;
1514 // Using a different name for skills that must be confirmed makes handling this easier.
1515 const char *action = (skill.MustConfirm && !AllEpisodes[gs->Episode].mNoSkill) ?
1516 "StartgameConfirm" : "Startgame";
1517
1518 FString *pItemText = NULL;
1519 if (gs->PlayerClass != NULL)
1520 {
1521 pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass);
1522 }
1523 li = new FOptionMenuItemSubmenu(pItemText? *pItemText : skill.MenuName, action, i);
1524 od->mItems.Push(li);
1525 if (!done)
1526 {
1527 done = true;
1528 od->mSelectedItem = M_GetDefaultSkill();
1529 }
1530 }
1531 }
1532
1533 //=============================================================================
1534 //
1535 // Returns the default skill level.
1536 //
1537 //=============================================================================
1538
M_GetDefaultSkill()1539 int M_GetDefaultSkill()
1540 {
1541 int defskill = DefaultSkill;
1542 if ((unsigned int)defskill >= AllSkills.Size())
1543 {
1544 defskill = (AllSkills.Size() - 1) / 2;
1545 }
1546 return defskill;
1547 }
1548