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 = &sect->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